I have a function that creates an array and I want to return the array to the caller:
create_array() {
local my_list=("a", "b", "c")
echo "${my_list[@]}"
}
my_algorithm() {
local result=$(create_array)
}
With this, I only get an expanded string. How can I "return" my_list without using anything global?
I tried various implementations, and none preserved arrays that had elements with spaces ... because they all had to use
echo
.Solution
Then I came across Dennis Williamson's answer. I incorporated his method into the following functions so they can a) accept an arbitrary array and b) be used to pass, duplicate and append arrays.
Then, other functions can return an array using catchable output or indirect arguments.
Bash can't pass around data structures as return values. A return value must be a numeric exit status between 0-255. However, you can certainly use command or process substitution to pass commands to an eval statement if you're so inclined.
This is rarely worth the trouble, IMHO. If you must pass data structures around in Bash, use a global variable--that's what they're for. If you don't want to do that for some reason, though, think in terms of positional parameters.
Your example could easily be rewritten to use positional parameters instead of global variables:
This all creates a certain amount of unnecessary complexity, though. Bash functions generally work best when you treat them more like procedures with side effects, and call them in sequence.
If all you're worried about is polluting your global namespace, you can also use the unset builtin to remove a global variable after you're done with it. Using your original example, let my_list be global (by removing the local keyword) and add
unset my_list
to the end of my_algorithm to clean up after yourself.With Bash version 4.3 and above, you can make use of a nameref so that the caller can pass in the array name and the callee can use a nameref to populate the named array, indirectly.
Produces the output:
You could make the function update an existing array as well:
This is a more elegant and efficient approach since we don't need command substitution
$()
to grab the standard output of the function being called. It also helps if the function were to return more than one output - we can simply use as many namerefs as the number of outputs.Here is what the Bash Manual says about nameref:
I needed a similar functionality recently, so the following is a mix of the suggestions made by RashaMatt and Steve Zobell.
As far as I can see, strings are kept intact and whitespaces are preserved.
Some more variations…
If your source data is formatted with each list element on a separate line, then the
mapfile
builtin is a simple and elegant way to read a list into an array:Note that, as with the
read
builtin, you would not ordinarily* usemapfile
in a pipeline (or subshell) because the assigned array variable would be unavailable to subsequent statements (* unless bash job control is disabled andshopt -s lastpipe
is set).A pure bash, minimal and robust solution based on the 'declare -p' builtin — without insane global variables
This approach involves the following three steps:
myVar="$( declare -p myArray )"
The output of the
declare -p
statement can be used to recreate the array. For instance the output ofdeclare -p myVar
might look like this:declare -a myVar='([0]="1st field" [1]="2nd field" [2]="3rd field")'
${myVar#*=}
Example 1 - return an array from a function
Output of Example 1:
Example 2 - pass an array to a function
Output of Example 2: