1

I have been trying to return a bash array from a bash function into a variable. However, no matter what I do, if any of the element contains whitespaces then things are going south. I suspect the problem is the way I have been storing the function output to the variable(my_arr=($(my_function)). A similar question is asked here but it didn't work for my usecase.

Here is a sample function:

my_function()
{
    my_arr=("random dude" EU 10 "boss" US 20)

    echo "${my_arr[@]}"
}

my_arr=($(my_function))

#printing individual elements of the array, Here only things started to mess up
# zeroth element is printed as 'random' not 'random dude'

echo "zeroth: ${my_arr[0]}"
echo "first:  ${my_arr[1]}"
echo "second: ${my_arr[2]}"
echo "third:  ${my_arr[3]}"
echo "forth:  ${my_arr[4]}"
echo "fifth:  ${my_arr[5]}"

#so any attempt of looping is irrelavent
echo "----Attempt-1-------"
for elem in "${my_arr[@]}"; do
    echo "${elem}";
done

echo "---Attempt-2--------"
for elem in $(printf "%q " "${my_arr[@]}") ;do
    echo "$elem"
done

Current output:

zeroth: random
first:  dude
second: EU
third:  10
forth:  boss
fifth:  US

I was expecting:

zeroth: random dude
first:  EU
second: 10
third:  boss
forth:  US
fifth:  20

Is there a way I can reliably store the array returned/printed by function outside of the function? I am on a legacy system where I do not have access to python, its a bash (with tools like awk, sed, etc) and perl only environment.

5
  • 1
    You can't manipulate arrays as a single value. Your function should instead define a global array that the caller can subsequently use. Commented Jan 30, 2023 at 17:40
  • 1
    echo ${my_arr[@]} simply writes a single space-separate string to standard output; any structure store using my_arr is lost. Commented Jan 30, 2023 at 17:41
  • 2
    Arrays exist because there is no way to reliably pass structured information via a single string. I would recommend learning Perl if it's the only other language available to you. Commented Jan 30, 2023 at 17:42
  • You could pass the name of the array as a function parameter, and then use a nameref. Commented Jan 30, 2023 at 17:54
  • It's important to note that Bash functions don't return in the same way Python functions do. They only return per se a status code, so any other outputs are done to the standard streams. Or they can affect the caller's environment, like we're recommending here. Commented Jan 30, 2023 at 17:54

2 Answers 2

4

Generally speaking, assignments made in the function are automatically available in the 'parent', so there's no need to 'pass' the values from the function to the 'parent'.

This means you could do something as simple as:

my_function()
{
    my_arr=("random dude" EU 10 "boss" US 20)
}

my_function

typeset -p my_arr

This generates:

declare -a my_arr=([0]="random dude" [1]="EU" [2]="10" [3]="boss" [4]="US" [5]="20")

This (obviously) assumes the 'parent' knows the name of the array (my_arr in this case) that's created in the function.

For a more dynamic solution you can use namerefs, eg:

my_function()
{
    local -n local_arr="$1"                         # local variable "local_arr" becomes a reference to whatever is passed on the function command line
    local_arr=("random dude" EU 10 "boss" US 20)    # assign array entries to the "$1" array via the local reference "local_arr"
}

The 'parent' can then pass an array name to the function, eg:

$ my_function my_arr
$ typeset -p my_arr
declare -a my_arr=([0]="random dude" [1]="EU" [2]="10" [3]="boss" [4]="US" [5]="20")

$ my_function another_array
$ typeset -p another_array
declare -a another_array=([0]="random dude" [1]="EU" [2]="10" [3]="boss" [4]="US" [5]="20")
Sign up to request clarification or add additional context in comments.

3 Comments

Side note: namerefs require Bash 4.3+, so they're not available in the standard macOS Bash, for example.
@Benjamin Can macos bash/pre-4.3 bash run declare -p?
@TylerStoney Yes, that was around at least as far back as Bash 2.05, as far as I can tell from CHANGES.
1

If you have access to Bash 4.3+, use @markp's answer. Otherwise, assuming the shell can still run declare you can kinda cheese the same thing, though it looks hideous:

myfunction() {
    # create and populate your array
    local myarr=("random guy" EU 10 "boss" US 20)

    # declare -p works effectively the same as typeset.
    # We store its output - the variable's structure/definition - 
    # so we can bring the variable to life later on.
    # Take that, Frankenstein!
    var=$(declare -p "myarr")

    # replace the name 'myarr' with the one we passed in
    eval "$1="${var#*=}
}

myfunction "new_array_name"
# It's alive!!
for x in "${new_array_name[@]}"; do
    ...

7 Comments

Please don't promote returning value by command substitution. You can simply use eval directly here. Replace echo with eval and call the function normally. Also remove declare -a.
What should I prefer to command substitution? Take advantage of bash's default global variable behavior? What makes it so devious?
Yes other than returning value by reference, you can use a generic global variable and assign the value to the proper variable after function call. I use __. The problem is promoting expensive shell forking.
I also gave you a suggestion already. Replace echo with eval. That is another solution that doesn't use a global. At least not a static one.
@konsolebox you are not really answering his question "What makes it so devious?". I'd like to know too.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.