How to Use Global Arrays in Bash

Associative arrays are local by default


From: Greg Wooledge

Sent: Tue, 23 Aug 2011 06:53:27 -0700

Subject: Re: YAQAGV (Yet Another Question About Global Variables)

bash 4.2 adds "declare -g" to create global variables from within a
function.

Thank you Greg! However Debian Squeeze still has Bash 4.1.5

How to use global arrays in bash?

The | while read element; do ... done is running in a sub-shell, so its updates to the global are lost when the sub-shell exits.

One solution is to use bash's 'process substitution' to get the input to the while loop to run in a subshell instead. See:
Reading multiple lines in bash without spawning a new subshell?

declare global array in shell

There are several flaws to your script :

  • Your if tests should be written with [[, not [, which is for binary comparison (more info : here). If you want to keep [ or are not using bash, you will have to quote your line variable, i.e. write all your tests like this : if [ -f "$line" ];then

  • Don't use ls to list the current directory as it misbehaves in some cases. A glob would be more suited in your case (more info: here)

  • If you want to avoid using a pipe, use a for loop instead. Replace ls | while read line with for line in $(ls) or, to take my previous point in acount, for line in *

After doing that, I tested your script and it worked perfectly fine. You should note that some folders will be listed under both under "executable files" and "directories", due to them having +x rights (I don't know if this is the behaviour you wanted).

As a side note, you don't need to declare variables in bash before using them. Your first 6 lines are thus un-necessary. Variables i,j,k are not necessary as well as you can dynamicaly increment an array with the following syntax : normal+=("$line").

Bash - Array length (assignment) of Local Variable vs Global Variable


How do I correctly assign a local variable to receive array as input?

It doesn't matter whether the variables are local or global. In both cases, use parentheses like you would do in a manual array definition.

array=("${otherArray[@]}")

Supply arguments by command-line preferrably --- but I assume giving an array on command-line might be different

You can supply one array just fine

f() {
local x=("$@")
declare -p x
}
a=(1 2 3)
f "${a[@]}"

But for multiple arrays (as in f "${a[@]}" "${b[@]}") there is a problem. The function does not know where the first array ends and the second array starts. You could

  • indicate the start/end by specifying the length of the first array


f() {
local xlen="$1";
shift
x=("${@:1:xlen}") y=("${@:xlen+1}")
declare -p x y
}
...
f "${#a[@]}" "${a[@]}" "${b[@]}"
  • indicate the start/end by inserting a delimiter (f "${a[@]}" , "${b[@]}") which must not appear in the arrays.


f() {
local dpos=$(printf %s\\0 "$@" | grep -zFxnm1 DELIM | sed 's/:.*//');
x=("${@:1:dpos-1}") y=("${@:dpos+1}")
declare -p x y
}
...
f "${a[@]}" DELIM "${b[@]}"
  • do not pass the arrays, but only their names (f a b).

How to declare a global array within a function without the -g option?

As recommended by gniourf_gniourf, you can use printf -v (in bash 4.1 or later)

printf -v "${fruit[i]}[x]" "%s" "$var"

or read

read "${fruit[i]}[x]" <<< "$var"

You probably just need to make sure the expression passed to eval contains appropriate quotes.

$ fruit=(apple pear orange)
$ f () { eval "${fruit[$1]}[$2]=\"$3\""; }
$ f 0 2 'hi there' # apple[2]='hi there'
$ declare -p apple
declare -a apple='([2]="hi there")'

It should go without saying that this isn't recommended, given the risk for arbitrary code execution.

Let a function modify a global variable

You are passing, in effect, the names of each element of the array. Just pass the name of the array itself. Then use declare to set the value of any particular element using indirect parameter expansion.

function my_function(){
elt2="$1[2]"
declare "$elt2=true"
}

my_function my_array

In bash 4.3, named references make this much simpler.

function my_function () {
declare -n arr=$1
arr[2]=true
}

my_function my_array

This only makes sense, though, if you intend to use my_function with different global arrays. If my_function is only intended to work with the global my_array, then you aren't gaining anything by this: just work with the global as-is.

function my_function () {
my_array[2]=true
}

Bash script - global variable becomes empty when function exits

As i said in comments, you can use simple function calling, simple changes to your test code will be as follow:

   #!/bin/bash
declare GLOBAL_VAR
function callThis(){
local output="==========================================\n"
output="$output [START callThis()]\n"
# For demonstration only - error 5 might be a built-in bash error code, but if caller
# expects 0/5 they can be handled
local statusCode=5
# This variable is empty as soon as it exits the function
GLOBAL_VAR="Some array value OR text output into GLOBAL_VAR"
# Because the script has alot of text (echo'd/printf) output, and the function
# calculates a result (array), I would like to have it as a RESULT
# for another function.
output="$output This is some output that will be sent to caller.\n"
output="$output Test to see if GLOBAL_VAR is assigned:\n"
output="$output '$GLOBAL_VAR'\n"
output="$output [END callThis()]\n"
output="$output ==========================================\n"
echo -e $output
return $((statusCode))
}

function startHere(){
# OUTPUT=$(callThis)
callThis
STATUS_RESULT=$?
# echo -e "[Text output : $OUTPUT\n]"
echo -e "[Status Code : $STATUS_RESULT\n]"
echo -e "[Global VAR in startHere(): '$GLOBAL_VAR']"
if [ "$STATUS_RESULT" -eq 5 ]; then
echo "Success! Status code $STATUS_RESULT found!"
fi
}
# echo -e $(startHere)
startHere
echo "Global VAR is now (outside functions): '$GLOBAL_VAR'"

output:

==========================================
[START callThis()]
This is some output that will be sent to caller.
Test to see if GLOBAL_VAR is assigned:
'Some array value OR text output into GLOBAL_VAR'
[END callThis()]
==========================================

[Status Code : 5
]
[Global VAR in startHere(): 'Some array value OR text output into GLOBAL_VAR']
Success! Status code 5 found!
Global VAR is now (outside functions): 'Some array value OR text output into GLOBAL_VAR'

also this can be helpful as an explanation for other methods for using sub-shell

Can't access global associate array using indirect expansion?

You didn't declare local_arr1 as an associative array. It springs into existence with

local_arr1=( [key]="local val" )

so bash creates a normal array for you, with key understood as a variable whose value is the index in the array (zero in this case, as there's no $key). You can test it with set -eu or key=1.

Note that the correct way to use indirection on arrays is to include the index in the string:

arr1[2]=x
i=1
j=2
tmp=arr$i[$j]
echo ${!tmp}


Related Topics



Leave a reply



Submit