12

I am trying to define a local array in a bash function and access it outside that function.

I realise that BASH functions do not return values but I can assign the results of a calculation to a global value. I expected this code to echo the content of array[] to the screen. I'm not sure why its failing.

function returnarray
{
local array=(foo doo coo)
#echo "inside ${array[@]}"
}


targetvalue=$(returnarray)
echo ${targetvalue[@]}
1
  • What does it say? Did you check this topic. Maybe you're missing () after function name. Commented Mar 14, 2015 at 9:16

6 Answers 6

19

You have two options. The first one is what @choroba prescribes, and it's probably the best and simplest: don't define your array local.

returnarray() {
    array=(foo doo coo) # NOT local
}

# call your function
returnarray
# now the array is in array and you may copy it for later use as follows:
targetvalue=( "${array[@]}" )
# print it to check:
declare -p targetvalue

This is neat, simple, safe, completely avoids the use of subshells (so it's more efficient). It has one caveat, though: it won't work with sparse arrays (but this should be a minor detail). There's another tiny disadvantage: the array needs to be copied.


Another option is to pass a variable name to your function, and have the function generate the array directly. This uses namerefs and is only available since Bash 4.3 (but it's really good—use it if you can!):

generatearray() {
    # $1 is array name in which array is generated
    local -n array="$1" || return 1
    array=( foo doo coo )
}
# call function that constructs the array with the array name
generatearray targetvalue
# display it
declare -p targetvalue
Sign up to request clarification or add additional context in comments.

Comments

4

To make the variable accessible from outside, don't declare it local. Make it global.

Comments

1

First, as you say, there are no return values of bash functions. So the only way to pass a local value is to echo it.

However, this would lead to your targetvalue having everything you echoed in index 0 if interpreted as an array. To tell bash to treat the parts as array parts, you have to surround them by parentheses - from the bash manual:

Arrays are assigned to using compound assignments of the form name=(value1 ... valuen), where each value is of the form [sub‐ script]=string.

#!/bin/bash

function returnarray
{
    local array=(foo doo coo)
    echo "${array[@]}"
}


targetvalue=($(returnarray))
echo ${targetvalue[@]}
echo ${targetvalue[1]}

However, all of this is really programming around how bash works. It will be better to define your arrays globally.

As the use of echo makes bash interpret the values, this only works, if the values of the array are not affected by bash, for example the values may not contain wildcards or spaces, wildcards would be expanded to matching files and spaces in a value would translate into multiple array values.

3 Comments

This is subject to pathname expansion too.
@gniourf_gniourf: maybe too many limitations with this approach. I am wondering if I should delete it all together.
That's up to you… it's actually good that you recognize the serious drawbacks. Usually people advocating this kind of approach will try to fix the flaws: for one, they'll use set -f to disable pathname expansion. Then, regarding the spaces, they'll do something crazy, like setting IFS=$'\n' and instead of echo "${array[@]}" would use printf '%s\n' "${array[@]}". Then, after being pointed that it still breaks with newlines in fields, they'll either surrender (but this only happens with intelligent people) or claim that it's unlikely or will use printf with %q followed by an eval.
0

Look at this, it might be ok with spaces and other characters....

#!/bin/bash
function returnarray
{
newname=$1
local array=(foo doo coo)
declare -p array | sed "s/array/$newname/g"
}

eval $(returnarray glob)
echo elm0 ${glob[0]}
echo elm1 ${glob[1]}
echo elm2 ${glob[2]}

See How to return an array in bash without using globals?

Edit: the comment about 'array' was correct... here is a fixed version. sed usage I dont mind...

#!/bin/bash
function returnarray
{
newname=$1
local array=(foo doo coo "declare -a array" aa)
declare -p array | sed "s/^declare -a array/declare -a $newname/"
}

eval $(returnarray glob)
echo elm0 ${glob[0]}
echo elm1 ${glob[1]}
echo elm2 ${glob[2]}
echo elm2 ${glob[3]}
echo elm2 ${glob[4]}

2 Comments

That's good, thanks but I do wonder if eval is suitable in a use-case where the array will contain user input data. mywiki.wooledge.org/BashFAQ/048
@Dave: as written (without quotes) this is not safe at all. Moreover, the solution is broken by the use of sed: if array appears in any field of the array, there will be problems… and it's an ugly solution as it's not pure Bash :).
0

You can declare local variables, and then printf a comma-separated-value string, which converts nicely to an array. The key is using printf instead of echo.

http://wiki.bash-hackers.org/commands/builtin/read :

printf is used, because (depending on settings), echo may interpret some baskslash-escapes or switches (like -n).

Code:

#Return csv of project.id's
function get_project() {
  local qry="select id from ${DB}.projects where identifier=\"${1}\";"
  local ids=`sudo -u ${DB_USER} ${DB_LOCATION} -u ${DB_USER} -p${DB_PASS} -s -e "${qry}"`
  #return
  while read -r element; do printf "%s," "$element"; done <<< "$ids"
}
#Return csv of member.id's
function get_members() {
  local qry="select user_id from ${DB}.members where project_id=${1};"
  local ids=`sudo -u ${DB_USER} ${DB_LOCATION} -u ${DB_USER} -p${DB_PASS} -s -e "${qry}"`
  #return
  while read -r element; do printf "%s," "$element"; done <<< "$ids"
}
projects=( $(get_project "newuser1") )
declare -p projects
member_ids=( $(get_members $projects) )
declare -p member_ids

Terminal:

root@dev:~# ./batch_memberships.sh
declare -a projects='([0]="439")'
declare -a member_ids='([0]="315" [1]="1")'

Comments

0

Another technique is to use line-oriented data, then load an array with mapfile (a.k.a. readarray).

Here I'm using printf '%s\n' to print one element per line in the function, and to show the result.

function return_array_lines
{
  local array=(foo doo coo)
  # print one element per line
  printf '%s\n' "${array[@]}" 
}

# use mapfile to read lines into a new array
mapfile -t targetvalue < <(return_array_lines)

# print the new array
printf '=>"%s"\n' "${targetvalue[@]}"
=>"foo"
=>"doo"
=>"coo"

This presumes your data does not have newlines (which can be encoded). If you need support for arbitrary strings, it's probably worth using JSON to encode, or graduate to a more powerful language.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.