3

I need to fill an array inside a function, passing it as an argument and NOT using a global variable (the function will be called here and there with different arrays).

I've read this discussion bash how to pass array as an argument to a function and used the pass-by-name solution, that ALMOST do the trick.

Here is my code

#!/bin/bash

function fillArray {
  arrayName=$1[@]
  array=("${!arrayName}")
  for i in 0 1 2 3
  do
    array+=("new item $i")
  done

  echo "Tot items in function: ${#array[@]}"
  for item in "${array[@]}"
  do
    echo $item
  done
  echo
}

myArray=("my item 0" "my item 1")

fillArray myArray

echo "Tot items in main: ${#myArray[@]}"
for item in "${myArray[@]}"
do
  echo $item
done

Here is the output

Tot items in function: 6
my item 0
my item 1
new item 0
new item 1
new item 2
new item 3

Tot items in main: 2
my item 0
my item 1

So, the function correctly use the array passed as parameter (first two items are the one added in main declaration), and append the new items to the array. After the function call, in main, the added items are lost.

What am I missing? Probably some pass-by-reference/pass-by-copy things...

Thanks!

0

4 Answers 4

5

For future reference, this becomes trivial with named references in bash 4.3:

function fillArray {
  declare -n arrayName=$1

  for i in 0 1 2 3
  do
    arrayName+=("new item $i")
  done

  echo "Tot items in function: ${#arrayName[@]}"
  for item in "${arrayName[@]}"
  do
    echo $item
  done
  echo
}
Sign up to request clarification or add additional context in comments.

4 Comments

Note that many distributions are just starting to ship with bash 4.2, so 4.3 may be a long wait if you don't want to upgrade yourself.
ksh has had nameref variables for some time. Nice to see bash catching up for this very useful feature.
It will be a great answer, once you can reasonably expect someone to be using bash 4.3 :)
@glennjackman Indeed. I need to spend some time learning about ksh, if only to know what all bash has borrowed :)
0

In this answer using such technique. Modified, you can

addtoarray () { var="$1"; shift 1; eval "$var+=($(printf "'%s' " "$@"))"; }

arr=('my item 0' 'my item 1')
printf "%s\n" "${arr[@]}"
echo ====
addtoarray arr 'my new item 2' 'my new item 3'
printf "%s\n" "${arr[@]}"

prints

my item 0
my item 1
====
my item 0
my item 1
my new item 2
my new item 3

works also for initializing arrays

addtoarray secarr $(seq 5)
printf "%s\n" "${secarr[@]}"

prints:

1
2
3
4
5

EDIT

Decomposition of the function - works as code generator

addtoarray () { var="$1"; shift 1; eval "$var+=($(printf "'%s' " "$@"))"; }
  • the function's 1st argument is the variable name(!!!) (not value)
  • storing the name (in $1) it into $var and shifting it out from the arguments
  • now the arguments contains only new elements for the array like val1 val2
  • now constructing an bash command:
somearrayname+=('elemnts1' 'element2' .... )
  • where the somearrayname is the variable name what we passed as the 1st arg
  • the printf "'%s '" "$@" creates the single-quoted array members from the arg-list
  • so in case of calling the function as addtoarray arr val1 "spaced val2" we generating the next string
arr+=('val1' 'spaced val2' )

this is an correct construction to adding members to the array named arr - regardless if before contets, e.g. adds the new elements to its end. (if it was empty, the end is its start)

  • finally the eval executes the above generated string
  • in the result you got an initialized/modified array $arr (the $arr is global variable, therefore is visible in the function too)

And finally - brutally stole @chepner's answer ;) using his technique the addtoarray si simple as:

addtoarray () {
    declare -n arrname=$1
    shift 1;
    arrname+=("$@")
}

If you have bash 4.3 you should accepts @chepner's answer - it is the best.

5 Comments

Your code does the trick. But what's happening? In main the array is filled with the new value. In the function, var seems to only carry the main variable name (it's not an array, like I thought).
@il_mix added the explanation
Obligatory warning: eval will execute anything you pass as a parameter; it doesn't care if the generated string winds up being more than a simple array assignment
@chepner - yes, of course - in this case before the eval all passed arguments are stored into single quoted strings what helps a bit... ;) (should sanitize the array name tough)...
Please replace eval "$var+=($(printf "'%s' " "$@"))"; with eval "$var+=($(printf '%q ' "$@"))";
0

You are adding elements into an array variable inside the function but arrays or any other variables are not passed as reference in BASH. So any changes made in those variables inside the function won't be visible outside the function.

As a workaround you can use a global variable like this example:

# initialize an array
myArray=("my item 0" "my item 1")

useArrayInFunc { myArray+=("my item 2")); }

useArrayInFunc

printf "%s\n" "${myArray[@]}"
my item 0
my item 1
my item 2

2 Comments

I prefer not to use a global variable
Since I need to use the function on different arrays, and then do some operations with these arrays, I need to call it with different parameters. Otherwise, I can let the function operate on a global variable, and after the function call copy the global array values to the actual array; this is kind of tedious...
0

You need to assign the local var to the global one:

function fillArray {
  local arrayName=$1
  local ref=$arrayName[@]
  local array=("${!ref}")
  local i item key

  for i in 0 1 2 3
  do
    array+=("new item $i")
  done

  echo "Tot items in function: ${#array[@]}"
  for item in "${array[@]}"
  do
    echo $item
  done
  echo

  for key in "${!array[@]}"; do
    declare -g ${arrayName}["$key"]="${array[$key]}"
  done
}

5 Comments

I tried your example, but I get this error: "declare: -g: invalid option". I'm using bash 4.1.2
Bummer. Working with arrays in bash is such a PITA. You might want to change your approach: call the function to generate the new items to add to the array, but return (via 'echo') a newline/null-byte delimited string. Add the elements to the array in the current scope, not in the function.
confirmed that -g was added to declare in version 4.2
bash 4.2 is 3 years old now. Any reason you can't upgrade?
I'm on a Windows computer at the moment, using an online (old) bash service. I will test at home with an updated bash.

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.