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.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
monk
  • 1,953
  • 3
  • 21
  • 41
  • 1
    You can't manipulate arrays as a single value. Your function should instead define a global array that the caller can subsequently use. – chepner Jan 30 '23 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. – chepner Jan 30 '23 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. – chepner Jan 30 '23 at 17:42
  • You could pass the name of the array as a function parameter, and then use a [nameref](https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameters). – Benjamin W. Jan 30 '23 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. – wjandrea Jan 30 '23 at 17:54

2 Answers2

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")
markp-fuso
  • 28,790
  • 4
  • 16
  • 36
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
    ...
Tyler Stoney
  • 418
  • 3
  • 13
  • 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`. – konsolebox Jan 30 '23 at 19:19
  • What should I prefer to command substitution? Take advantage of bash's default global variable behavior? What makes it so devious? – Tyler Stoney Jan 30 '23 at 19:45
  • 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. – konsolebox Jan 30 '23 at 19:52
  • 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 Jan 30 '23 at 19:57
  • @konsolebox you are not really answering his question "What makes it so devious?". I'd like to know too. – rbhkamal Jan 30 '23 at 20:22
  • @rbhkamal I'm not even sure how to answer that to be honest. Maybe he can use another word. If he meant "devious" as something like being "hacky", it doesn't matter. A lot of things in Bash are already hacky and it wasn't my point. I already said what I meant: The problem is promoting expensive shell forking. – konsolebox Jan 30 '23 at 21:08
  • I should have just said "what's wrong with it?" "It's expensive" is a totally reasonable answer. Thanks! – Tyler Stoney Jan 30 '23 at 21:33