2

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!

Community
  • 1
  • 1
il_mix
  • 553
  • 8
  • 20

4 Answers4

3

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
}
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 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. – chepner Sep 05 '14 at 13:23
  • 1
    ksh has had nameref variables for some time. Nice to see bash catching up for this very useful feature. – glenn jackman Sep 05 '14 at 13:26
  • 1
    It *will* be a great answer, once you can reasonably expect someone to be using `bash` 4.3 :) – chepner Sep 05 '14 at 13:35
  • @glennjackman Indeed. I need to spend some time learning about `ksh`, if only to know what all `bash` has borrowed :) – chepner Sep 05 '14 at 14:00
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
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • I prefer not to use a global variable – il_mix Sep 05 '14 at 11:49
  • 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... – il_mix Sep 05 '14 at 13:13
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
}
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • I tried your example, but I get this error: "declare: -g: invalid option". I'm using bash 4.1.2 – il_mix Sep 05 '14 at 11:50
  • 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. – glenn jackman Sep 05 '14 at 13:13
  • confirmed that `-g` was added to `declare` in version 4.2 – glenn jackman Sep 05 '14 at 13:18
  • bash 4.2 is 3 years old now. Any reason you can't upgrade? – glenn jackman Sep 05 '14 at 13:19
  • I'm on a Windows computer at the moment, using an online (old) bash service. I will test at home with an updated bash. – il_mix Sep 05 '14 at 13:54
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.

Community
  • 1
  • 1
clt60
  • 62,119
  • 17
  • 107
  • 194
  • 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 Sep 05 '14 at 12:03
  • @il_mix added the explanation – clt60 Sep 05 '14 at 13:04
  • 1
    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 Sep 05 '14 at 13:19
  • @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)... – clt60 Sep 05 '14 at 13:22
  • Please replace `eval "$var+=($(printf "'%s' " "$@"))";` with `eval "$var+=($(printf '%q ' "$@"))";` – Fravadona Nov 15 '21 at 13:52