Passing arrays as parameters in bash

Viewed 182940

How can I pass an array as parameter to a bash function?

Note: After not finding an answer here on Stack Overflow, I posted my somewhat crude solution myself. It allows for only one array being passed, and it being the last element of the parameter list. Actually, it is not passing the array at all, but a list of its elements, which are re-assembled into an array by called_function(), but it worked for me. If someone knows a better way, feel free to add it here.

15 Answers

Note: This is the somewhat crude solution I posted myself, after not finding an answer here on Stack Overflow. It allows for only one array being passed, and it being the last element of the parameter list. Actually, it is not passing the array at all, but a list of its elements, which are re-assembled into an array by called_function(), but it worked for me. Somewhat later Ken posted his solution, but I kept mine here for "historic" reference.

calling_function()
{
    variable="a"
    array=( "x", "y", "z" )
    called_function "${variable}" "${array[@]}"
}

called_function()
{
    local_variable="${1}"
    shift
    local_array=("${@}")
}

DevSolar's answer has one point I don't understand (maybe he has a specific reason to do so, but I can't think of one): He sets the array from the positional parameters element by element, iterative.

An easier approuch would be

called_function()
{
  ...
  # do everything like shown by DevSolar
  ...

  # now get a copy of the positional parameters
  local_array=("$@")
  ...
}

Modern bash (apparently version 4.3 or later), allows you to pass arrays by reference. I'll show that below. If you'd like to manually serialize and deserialize the arrays instead, see my answer here for bash regular "indexed" arrays, and here for bash associative arrays. Passing arrays by reference, as shown below, is much easier and more-concise, however, so that's what I now recommend.

The code below is also available online in my eRCaGuy_hello_world repo here: array_pass_as_bash_parameter_by_reference.sh. See also this example here: array_pass_as_bash_parameter_2_associative.sh.

Here is a demo for regular bash arrays:

function foo {
    # declare a local **reference variable** (hence `-n`) named `data_ref`
    # which is a reference to the value stored in the first parameter
    # passed in
    local -n data_ref="$1"
    echo "${data_ref[0]}"
    echo "${data_ref[1]}"
}

# declare a regular bash "indexed" array
declare -a data
data+=("Fred Flintstone")
data+=("Barney Rubble")
foo "data"

Sample output:

Fred Flintstone
Barney Rubble

...and here is a demo for associative bash arrays (ie: bash hash tables, "dictionaries", or "unordered maps"):

function foo {
    # declare a local **reference variable** (hence `-n`) named `data_ref`
    # which is a reference to the value stored in the first parameter
    # passed in
    local -n data_ref="$1"
    echo "${data_ref["a"]}"
    echo "${data_ref["b"]}"
}

# declare a bash associative array
declare -A data
data["a"]="Fred Flintstone"
data["b"]="Barney Rubble"
foo "data"

Sample output:

Fred Flintstone
Barney Rubble

References:

  1. I modified the above code samples from @Todd Lehman's answer here: How to pass an associative array as argument to a function in Bash?
  2. See also my manual serializing/deserializing answer here
  3. And see my follow-up Question here: Why do the man bash pages state the declare and local -n attribute "cannot be applied to array variables", and yet it can?

As ugly as it is, here is a workaround that works as long as you aren't passing an array explicitly, but a variable corresponding to an array:

function passarray()
{
    eval array_internally=("$(echo '${'$1'[@]}')")
    # access array now via array_internally
    echo "${array_internally[@]}"
    #...
}

array=(0 1 2 3 4 5)
passarray array # echo's (0 1 2 3 4 5) as expected

I'm sure someone can come up with a clearner implementation of the idea, but I've found this to be a better solution than passing an array as "{array[@]"} and then accessing it internally using array_inside=("$@"). This becomes complicated when there are other positional/getopts parameters. In these cases, I've had to first determine and then remove the parameters not associated with the array using some combination of shift and array element removal.

A purist perspective likely views this approach as a violation of the language, but pragmatically speaking, this approach has saved me a whole lot of grief. On a related topic, I also use eval to assign an internally constructed array to a variable named according to a parameter target_varname I pass to the function:

eval $target_varname=$"(${array_inside[@]})"

Hope this helps someone.

My short answer is:

function display_two_array {
    local arr1=$1
    local arr2=$2
    for i in $arr1
    do
       echo "arrary1: $i"
    done
    
    for i in $arr2
    do
       echo "arrary2: $i"
    done
}

test_array=(1 2 3 4 5)
test_array2=(7 8 9 10 11)

display_two_array "${test_array[*]}" "${test_array2[*]}"

It should be noticed that the ${test_array[*]} and ${test_array2[*]} should be surrounded by "", otherwise you'll fail.

The answer below shows you how to pass bash regular "indexed" arrays as parameters to a function essentially by serializing and deserializing them.

  1. To see this manual serializing/deserializing for bash associative arrays (hash tables) instead of for regular indexed arrays, see my answer here.
  2. For a better way (which requires bash version 4.3 or later, I think), which passes the arrays by reference, see the link just above and my other answer here.
    1. Passing arrays by reference is much easier and more-concise, so that's what I now recommend. That being said, the manual serializing/deserializing techniques I show below are also extremely informative and useful.

Quick summary:

See the 3 separate function definitions below. I go over how to pass:

  1. one bash array to a function
  2. two or more bash arrays to a function, and
  3. two or more bash arrays plus additional arguments (before or after the arrays) to a function.

12 years later and I still don't see any answers I really like here and which I would consider to be thorough enough, simple enough, and "canonical" enough for me to just use--answers which I can come back to again and again and copy and paste and expand when needed. So, here is my answer which I do consider to be all of these things.

How to pass bash arrays as parameters to bash functions

You might also call this "variadic argument parsing in bash functions or scripts", especially since the number of elements in each array passed in to the examples below can dynamically vary, and in bash the elements of an array essentially get passed to the function as separate input parameters even when the array is passed in via a single array expansion argument like this "${array1[@]}".

For all example code below, assume you have these two bash arrays for testing:

array1=()
array1+=("one")
array1+=("two")
array1+=("three")

array2=("four" "five" "six" "seven" "eight")

The code above and below is available in my bash/array_pass_as_bash_parameter.sh file in my eRCaGuy_hello_world repo on GitHub.

Example 1: how to pass one bash array to a function

To pass an array to a bash function, you have to pass all of its elements separately. Given bash array array1, the syntax to obtain all elements of this array is "${array1[@]}". Since all incoming parameters to a bash function or executable file get wrapped up in the magic bash input parameter array called @, you can read all members of the input array with the "$@" syntax, as shown below.

Function definition:

# Print all elements of a bash array.
# General form:
#       print_one_array array1
# Example usage:
#       print_one_array "${array1[@]}"
print_one_array() {
    for element in "$@"; do
        printf "    %s\n" "$element"
    done
}

Example usage:

echo "Printing array1"
# This syntax passes all members of array1 as separate input arguments to 
# the function
print_one_array "${array1[@]}"

Example Output:

Printing array1
    one
    two
    three

Example 2: how to pass two or more bash arrays to a function...

(and how to recapture the input arrays as separate bash arrays again)

Here, we need to differentiate which incoming parameters belong to which array. To do this, we need to know the size of each array, meaning the number of elements in each array. This is very similar to passing arrays in C, where we also generally must know the array length passed to any C function. Given bash array array1, the number of elements in it can be obtained with "${#array1[@]}" (notice the usage of the # symbol). In order to know where in the input arguments the array_len length parameter is, we must always pass the array length parameter for each array before passing the individual array elements, as shown below.

In order to parse the arrays, I use array slicing on the input argument array, @.

Here is a reminder on how bash array slicing syntax works (from my answer here). In the slicing syntax :start:length, the 1st number is the zero-based index to start slicing from, and the 2nd number is the number of elements to grab:

# array slicing basic format 1: grab a certain length starting at a certain
# index
echo "${@:2:5}"
#         │ │
#         │ └────> slice length
#         └──────> slice starting index (zero-based)

# array slicing basic format 2: grab all remaining array elements starting at a
# certain index through to the end
echo "${@:2}"
#         │
#         │
#         └──────> slice starting index (zero-based)

Also, in order to force the sliced parameters from the input array to become a new array, I surround them in parenthesis (), like this, for example ("${@:$i:$array1_len}"). Those parenthesis on the outside are important, again, because that's how we make an array in bash.

This example below only accepts two bash arrays, but following the given patterns it can be easily adapted to accept any number of bash arrays as arguments.

Function definition:

# Print all elements of two bash arrays.
# General form (notice length MUST come before the array in order
# to be able to parse the args!):
#       print_two_arrays array1_len array1 array2_len array2
# Example usage:
#       print_two_arrays "${#array1[@]}" "${array1[@]}" \
#       "${#array2[@]}" "${array2[@]}"
print_two_arrays() {
    # For debugging: print all input args
    echo "All args to 'print_two_arrays':"
    print_one_array "$@"

    i=1

    # Read array1_len into a variable
    array1_len="${@:$i:1}"
    ((i++))
    # Read array1 into a new array
    array1=("${@:$i:$array1_len}")
    ((i += $array1_len))

    # Read array2_len into a variable
    array2_len="${@:$i:1}"
    ((i++))
    # Read array2 into a new array
    array2=("${@:$i:$array2_len}")
    ((i += $array2_len))

    # Print the two arrays
    echo "array1:"
    print_one_array "${array1[@]}"
    echo "array2:"
    print_one_array "${array2[@]}"
}

Example usage:

echo "Printing array1 and array2"
print_two_arrays "${#array1[@]}" "${array1[@]}" "${#array2[@]}" "${array2[@]}"

Example Output:

Printing array1 and array2
All args to 'print_two_arrays':
    3
    one
    two
    three
    5
    four
    five
    six
    seven
    eight
array1:
    one
    two
    three
array2:
    four
    five
    six
    seven
    eight

Example 3: pass two bash arrays plus some extra args after that to a function

This is a tiny expansion of the example above. It also uses bash array slicing, just like the example above. Instead of stopping after parsing two full input arrays, however, we continue and parse a couple more arguments at the end. This pattern can be continued indefinitely for any number of bash arrays and any number of additional arguments, accommodating any input argument order, so long as the length of each bash array comes just before the elements of that array.

Function definition:

# Print all elements of two bash arrays, plus two extra args at the end.
# General form (notice length MUST come before the array in order
# to be able to parse the args!):
#       print_two_arrays_plus_extra_args array1_len array1 array2_len array2 \
#       extra_arg1 extra_arg2
# Example usage:
#       print_two_arrays_plus_extra_args "${#array1[@]}" "${array1[@]}" \
#       "${#array2[@]}" "${array2[@]}" "hello" "world"
print_two_arrays_plus_extra_args() {
    i=1

    # Read array1_len into a variable
    array1_len="${@:$i:1}"
    ((i++))
    # Read array1 into a new array
    array1=("${@:$i:$array1_len}")
    ((i += $array1_len))

    # Read array2_len into a variable
    array2_len="${@:$i:1}"
    ((i++))
    # Read array2 into a new array
    array2=("${@:$i:$array2_len}")
    ((i += $array2_len))

    # You can now read the extra arguments all at once and gather them into a
    # new array like this:
    extra_args_array=("${@:$i}")

    # OR you can read the extra arguments individually into their own variables
    # one-by-one like this
    extra_arg1="${@:$i:1}"
    ((i++))
    extra_arg2="${@:$i:1}"
    ((i++))

    # Print the output
    echo "array1:"
    print_one_array "${array1[@]}"
    echo "array2:"
    print_one_array "${array2[@]}"
    echo "extra_arg1 = $extra_arg1"
    echo "extra_arg2 = $extra_arg2"
    echo "extra_args_array:"
    print_one_array "${extra_args_array[@]}"
}

Example usage:

echo "Printing array1 and array2 plus some extra args"
print_two_arrays_plus_extra_args "${#array1[@]}" "${array1[@]}" \
"${#array2[@]}" "${array2[@]}" "hello" "world"

Example Output:

Printing array1 and array2 plus some extra args
array1:
    one
    two
    three
array2:
    four
    five
    six
    seven
    eight
extra_arg1 = hello
extra_arg2 = world
extra_args_array:
    hello
    world

References:

  1. I referenced a lot of my own sample code from my eRCaGuy_hello_world repo here:
    1. array_practice.sh
    2. array_slicing_demo.sh
  2. [my answer on bash array slicing] Unix & Linux: Bash: slice of positional parameters
  3. An answer to my question on "How can I create and use a backup copy of all input args ("$@") in bash?" - very useful for general array manipulation of the input argument array
  4. An answer to "How to pass array as an argument to a function in Bash", which confirmed to me this really important concept that:

    You cannot pass an array, you can only pass its elements (i.e. the expanded array).

See also:

  1. [another answer of mine on this topic] How to pass array as an argument to a function in Bash
Related