Passing arrays as parameters in bash
Asked Answered
B

16

222

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

Blastocyst answered 30/6, 2009 at 12:21 Comment(12)
Here you have nice reference and tons of examples.Rouge
Errr... Three downvotes on a five-year-old question within the same minute?Blastocyst
Here is a comprehensive answer: #6212719Synonymous
Related: Passing parameters to a Bash functionAceto
See also: How to pass array as an argument to a function in BashAceto
Here is the corresponding question for bash "associative arrays" (AKA: "dictionaries", "hash tables", or "unordered maps"): How to pass an associative array as argument to a function in Bash?Aceto
@GabrielStaples As there are no answer suggesting the use of getopts, please have a look at Run bash script with arrays as argument, using getoptsEntopic
@F.Hauri-GiveUpGitHub: Not the question here. This is about calling a bash function / subroutine, from within the script. Unless I am very much mistaken, getopts is for the command line used to call the overall script, which is similar, but distinct.Blastocyst
@Blastocyst Yes, you could use getopts in functions! I've edited my answer for adding a function sampleEntopic
@F.Hauri-GiveUpGitHub Still really really REALLY not what I was looking for, as you would still have to "markup" the array on calling the function. In this your answer does not offer any improvement over the answers below and is more complex / surprising, IMHO.Blastocyst
@DevSolar, passing by reference is pretty clean I think. I don't know if you've seen my answer on this since I added it 13 years late.Aceto
@GabrielStaples: Seen it, upvoted it back then. I'm no longer writing complex Bash scripts, and haven't been for a while, so I left good enough alone and didn't really check new answers on whether that checkmark might bear switching around... might do that when I find the time to re-evaluate.Blastocyst
B
238

You can pass multiple arrays as arguments using something like this:

takes_ary_as_arg()
{
    declare -a argAry1=("${!1}")
    echo "${argAry1[@]}"

    declare -a argAry2=("${!2}")
    echo "${argAry2[@]}"
}
try_with_local_arys()
{
    # array variables could have local scope
    local descTable=(
        "sli4-iread"
        "sli4-iwrite"
        "sli3-iread"
        "sli3-iwrite"
    )
    local optsTable=(
        "--msix  --iread"
        "--msix  --iwrite"
        "--msi   --iread"
        "--msi   --iwrite"
    )
    takes_ary_as_arg descTable[@] optsTable[@]
}
try_with_local_arys

will echo:

sli4-iread sli4-iwrite sli3-iread sli3-iwrite  
--msix  --iread --msix  --iwrite --msi   --iread --msi   --iwrite

Edit/notes: (from comments below)

  • descTable and optsTable are passed as names and are expanded in the function. Thus no $ is needed when given as parameters.
  • Note that this still works even with descTable etc being defined with local, because locals are visible to the functions they call.
  • The ! in ${!1} expands the arg 1 variable.
  • declare -a just makes the indexed array explicit, it is not strictly necessary.
Blintz answered 25/10, 2010 at 17:24 Comment(15)
One thing to note is that if the original array is sparse, the array in the receiving function won't have the same indices.Clare
This is brilliant, but can Ken or someone explain a couple of things that puzzle me about why it works: 1 - I would have thought that descTable and optsTable would have had to be prefixed with $ when passed as function arguments. 2 - In the first line of "takes...", why is an explicit array declaration needed? 3 - And what does the ! mean in the expression ${!1}, and why is [@] not required or even allowed there? -- This works, and all of these details seem to be needed based on my testing, but I would like to understand why!Tinfoil
1: descTable and optsTable are just passed as names, thus there is no $, they shall be expanded only in the called function 2: not totally sure, but I think it's not really necessary 3: the ! is used because the parameters passed to the function need to be expanded twice: $1 expands to "descTable[@]", and that should be expanded to "${descTable[@]}". The ${!1} syntax does just this.Estate
Interesting... this means, the local variables in the try_with_local_arys are visible in the functions that it calls, such as takes_ary_as_arg.Diaper
I don't think the "declare -a" part is necessary. The existence of parenthesis already define the LHS of the assignment as an array.Jacquetta
This solution depends on the documented "feature" that local variables are visible to the functions that they call. I'm glad there is a solution, but I can easily imagine cases where this feature make a bug hard to find. From The Advanced Bash Scripting Guide quoting the bash man page: '"Local can only be used within a function; it makes the variable name have a visible scope restricted to that function and its children." [emphasis added] The ABS Guide author considers this behavior to be a bug.' tldp.org/LDP/abs/html/localvar.htmlJog
With this solution, isn't the length of the second array effectively doubled in the inner function? This is because the spaces are re-interpreted.Reeba
It may be a good solution if you don't want to modify the content of the array.Adenaadenauer
What about associative arrays?Find
This answer helped me solve an issue just now. However, I wanted to point out that on my machine (using bash 4.3.42) the "${!1}" and "${!2}" need to have the quotes removed. If you do not, the value of the original array is read as one string and assigned to argAry1[0] and argAry2[0] respectively, basically meaning the array structure is lost.Alita
This doesn't work if there's files named descTable@ or optTable@ in the current directory. ;)Chromoprotein
This is 100% not different from passing no arguments and using ${descTable[@]}. The local keyword allows variables to leak to called functions, which is exactly what's being used here, just in an obfuscated manner.Plumbiferous
How does local arrays of one function descTable and optsTable expanded in other function takes_ary_as_arg by variable indirection ? someone please help me in understand this.Snowber
@SriramP as explained in a comment before (and I just updated the answer accordingly), because "local variables are visible to the functions that they call".Orsa
This is possible due to dynamic scoping in bash.Terrie
B
90

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=("${@}")
}
Blastocyst answered 30/6, 2009 at 12:25 Comment(7)
Three years after the fact, this answer - kept for historical reasons only - received two downvotes within a couple of days. As sadly usual on SO, without any note as to why people think this is warranted. Note that this answer predates all others, and that I accepted Ken's answer as the best solution. I am perfectly aware it is nowhere near perfect, but for four months it was the best available on SO. Why it should be downvoted two years after it took second place to Ken's perfect solution is beyond me.Blastocyst
@geirha: I would ask you to check who's posted the question, who posted this answer, and who probably accepted the answer you are calling "bad". ;-) You might also want to check the Note in the question, which points out why this solution is inferior to Ken's.Blastocyst
I know you asked the question, you wrote this answer, and that you accepted the bad answer. That's why I worded it that way. The reason the accepted answer is bad is because it is trying to pass array by reference, which is something you should really avoid. In addition, the example mashes multiple arguments into a single string. If you really need to pass arrays by reference, bash is the wrong language to begin with. Even with bash 4.3's new nameref variables, you cannot safely avoid name collisions (circular reference).Would
@geirha: So you deem Ken's answer, which is basically the only way you can pass (multiple) arrays in bash, as "bad" because there isn't a better way to do it in bash -- and mine, which cannot pass multiple arrays, as better? Think about that for a while...Blastocyst
Well, you can pass multiple arrays if you include the number of elements of each array. called_function "${#array[@]}" "${array[@]}" "${#array2[@]}" "${array2[@]}" etc... still with some obvious restrictions, but really, better to solve the problem in a way the language supports, rather than trying to bend the language into working the way you are used to in other languages.Would
@geirha: Well, I guess we'll have to agree that we don't agree, and you will have to let me being the judge of which answer answers my question best. Personally, I much prefer passing arrays by reference anyway (no matter the language, to save the data copying); even more so when the alternative is to bend over backwards and pass the array size as additional parameter...Blastocyst
Or, you can just go here for a comprehensive answer: #6212719Synonymous
L
41

Commenting on Ken Bertelson solution and answering Jan Hettich:

How it works

the takes_ary_as_arg descTable[@] optsTable[@] line in try_with_local_arys() function sends:

  1. This is actually creates a copy of the descTable and optsTable arrays which are accessible to the takes_ary_as_arg function.
  2. takes_ary_as_arg() function receives descTable[@] and optsTable[@] as strings, that means $1 == descTable[@] and $2 == optsTable[@].
  3. in the beginning of takes_ary_as_arg() function it uses ${!parameter} syntax, which is called indirect reference or sometimes double referenced, this means that instead of using $1's value, we use the value of the expanded value of $1, example:

    baba=booba
    variable=baba
    echo ${variable} # baba
    echo ${!variable} # booba
    

    likewise for $2.

  4. putting this in argAry1=("${!1}") creates argAry1 as an array (the brackets following =) with the expanded descTable[@], just like writing there argAry1=("${descTable[@]}") directly. the declare there is not required.

N.B.: It is worth mentioning that array initialization using this bracket form initializes the new array according to the IFS or Internal Field Separator which is by default tab, newline and space. in that case, since it used [@] notation each element is seen by itself as if he was quoted (contrary to [*]).

My reservation with it

In BASH, local variable scope is the current function and every child function called from it, this translates to the fact that takes_ary_as_arg() function "sees" those descTable[@] and optsTable[@] arrays, thus it is working (see above explanation).

Being that case, why not directly look at those variables themselves? It is just like writing there:

argAry1=("${descTable[@]}")

See above explanation, which just copies descTable[@] array's values according to the current IFS.

In summary

This is passing, in essence, nothing by value - as usual.

I also want to emphasize Dennis Williamson comment above: sparse arrays (arrays without all the keys defines - with "holes" in them) will not work as expected - we would loose the keys and "condense" the array.

That being said, I do see the value for generalization, functions thus can get the arrays (or copies) without knowing the names:

  • for ~"copies": this technique is good enough, just need to keep aware, that the indices (keys) are gone.
  • for real copies: we can use an eval for the keys, for example:

    eval local keys=(\${!$1})
    

and then a loop using them to create a copy. Note: here ! is not used it's previous indirect/double evaluation, but rather in array context it returns the array indices (keys).

  • and, of course, if we were to pass descTable and optsTable strings (without [@]), we could use the array itself (as in by reference) with eval. for a generic function that accepts arrays.
Lowe answered 26/7, 2011 at 10:5 Comment(1)
Good explanations of the mechanism behind Ken Bertelson explanation. To the question "Being that case, why not directly look at those variables themselves?", I will answer : simply for reuse of the function. Let's say I need to call a function with Array1, then with Array2, passing the array names becomes handy.Villanelle
K
25

The basic problem here is that the bash developer(s) that designed/implemented arrays really screwed the pooch. They decided that ${array} was just short hand for ${array[0]}, which was a bad mistake. Especially when you consider that ${array[0]} has no meaning and evaluates to the empty string if the array type is associative.

Assigning an array takes the form array=(value1 ... valueN) where value has the syntax [subscript]=string, thereby assigning a value directly to a particular index in the array. This makes it so there can be two types of arrays, numerically indexed and hash indexed (called associative arrays in bash parlance). It also makes it so that you can create sparse numerically indexed arrays. Leaving off the [subscript]= part is short hand for a numerically indexed array, starting with the ordinal index of 0 and incrementing with each new value in the assignment statement.

Therefore, ${array} should evaluate to the entire array, indexes and all. It should evaluate to the inverse of the assignment statement. Any third year CS major should know that. In that case, this code would work exactly as you might expect it to:

declare -A foo bar
foo=${bar}

Then, passing arrays by value to functions and assigning one array to another would work as the rest of the shell syntax dictates. But because they didn't do this right, the assignment operator = doesn't work for arrays, and arrays can't be passed by value to functions or to subshells or output in general (echo ${array}) without code to chew through it all.

So, if it had been done right, then the following example would show how the usefulness of arrays in bash could be substantially better:

simple=(first=one second=2 third=3)
echo ${simple}

the resulting output should be:

(first=one second=2 third=3)

Then, arrays could use the assignment operator, and be passed by value to functions and even other shell scripts. Easily stored by outputting to a file, and easily loaded from a file into a script.

declare -A foo
read foo <file

Alas, we have been let down by an otherwise superlative bash development team.

As such, to pass an array to a function, there is really only one option, and that is to use the nameref feature:

function funky() {
    local -n ARR

    ARR=$1
    echo "indexes: ${!ARR[@]}"
    echo "values: ${ARR[@]}"
}

declare -A HASH

HASH=([foo]=bar [zoom]=fast)
funky HASH # notice that I'm just passing the word 'HASH' to the function

will result in the following output:

indexes: foo zoom
values: bar fast

Since this is passing by reference, you can also assign to the array in the function. Yes, the array being referenced has to have a global scope, but that shouldn't be too big a deal, considering that this is shell scripting. To pass an associative or sparse indexed array by value to a function requires throwing all the indexes and the values onto the argument list (not too useful if it's a large array) as single strings like this:

funky "${!array[*]}" "${array[*]}"

and then writing a bunch of code inside the function to reassemble the array.

Kitti answered 31/3, 2015 at 21:41 Comment(3)
The solution of using local -n is better and more up to date than the accepted answer. This solution will also work for a variable of any type. The example listed in this answer can be shortened to local -n ARR=${1}. However the -n option for local/declare is only available in Bash version 4.3 and above.Macule
This is nice! Small gotcha: if you pass a variable with the same name as your function's local argument (e.g. funky ARR), shell will give warning circular name reference, because basically the function will try to do local -n ARR=ARR. Good discussion about this topic.Erickson
After reading that page, I reach the conclusion that: 1. Using arrays in bash is a very idea 2. Play with IFS and avoid arrays in bash if your idea was to write something elegant.Cotto
A
6

How to pass regular and associative arrays as parameters by reference in bash

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. For printing arrays by value or reference, see my answer here.

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?
Aceto answered 10/2, 2022 at 5:4 Comment(0)
R
5

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=("$@")
  ...
}
Reid answered 30/6, 2009 at 13:15 Comment(1)
My reason for not doing so is that I haven't toyed with bash arrays at all until a few days ago. Previously I'd have switched to Perl if it became complex, an option I don't have at my current job. Thanks for the hint!Blastocyst
W
4

An easy way to pass several arrays as parameter is to use a character-separated string. You can call your script like this:

./myScript.sh "value1;value2;value3" "somethingElse" "value4;value5" "anotherOne"

Then, you can extract it in your code like this:

myArray=$1
IFS=';' read -a myArray <<< "$myArray"

myOtherArray=$3
IFS=';' read -a myOtherArray <<< "$myOtherArray"

This way, you can actually pass multiple arrays as parameters and it doesn't have to be the last parameters.

Warmonger answered 23/12, 2016 at 17:13 Comment(0)
D
3
function aecho {
  set "$1[$2]"
  echo "${!1}"
}

Example

$ foo=(dog cat bird)

$ aecho foo 1
cat
Dodd answered 6/12, 2012 at 6:59 Comment(0)
O
1

This one works even with spaces:

format="\t%2s - %s\n"

function doAction
{
  local_array=("$@")
  for (( i = 0 ; i < ${#local_array[@]} ; i++ ))
    do
      printf "${format}" $i "${local_array[$i]}"
  done
  echo -n "Choose: "
  option=""
  read -n1 option
  echo ${local_array[option]}
  return
}

#the call:
doAction "${tools[@]}"
Onesided answered 25/5, 2011 at 6:55 Comment(2)
I wonder what the point is here. This is just normal argument passing. The "$@" syntax is made to work for spaces: "$@" is equivalent to "$1" "$2"...Indulge
Can I pass 2 arrays to a function?Poulterer
I
1

With a few tricks you can actually pass named parameters to functions, along with arrays.

The method I developed allows you to access parameters passed to a function like this:

testPassingParams() {

    @var hello
    l=4 @array anArrayWithFourElements
    l=2 @array anotherArrayWithTwo
    @var anotherSingle
    @reference table   # references only work in bash >=4.3
    @params anArrayOfVariedSize

    test "$hello" = "$1" && echo correct
    #
    test "${anArrayWithFourElements[0]}" = "$2" && echo correct
    test "${anArrayWithFourElements[1]}" = "$3" && echo correct
    test "${anArrayWithFourElements[2]}" = "$4" && echo correct
    # etc...
    #
    test "${anotherArrayWithTwo[0]}" = "$6" && echo correct
    test "${anotherArrayWithTwo[1]}" = "$7" && echo correct
    #
    test "$anotherSingle" = "$8" && echo correct
    #
    test "${table[test]}" = "works"
    table[inside]="adding a new value"
    #
    # I'm using * just in this example:
    test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct
}

fourElements=( a1 a2 "a3 with spaces" a4 )
twoElements=( b1 b2 )
declare -A assocArray
assocArray[test]="works"

testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..."

test "${assocArray[inside]}" = "adding a new value"

In other words, not only you can call your parameters by their names (which makes up for a more readable core), you can actually pass arrays (and references to variables - this feature works only in bash 4.3 though)! Plus, the mapped variables are all in the local scope, just as $1 (and others).

The code that makes this work is pretty light and works both in bash 3 and bash 4 (these are the only versions I've tested it with). If you're interested in more tricks like this that make developing with bash much nicer and easier, you can take a look at my Bash Infinity Framework, the code below was developed for that purpose.

Function.AssignParamLocally() {
    local commandWithArgs=( $1 )
    local command="${commandWithArgs[0]}"

    shift

    if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]]
    then
        paramNo+=-1
        return 0
    fi

    if [[ "$command" != "local" ]]
    then
        assignNormalCodeStarted=true
    fi

    local varDeclaration="${commandWithArgs[1]}"
    if [[ $varDeclaration == '-n' ]]
    then
        varDeclaration="${commandWithArgs[2]}"
    fi
    local varName="${varDeclaration%%=*}"

    # var value is only important if making an object later on from it
    local varValue="${varDeclaration#*=}"

    if [[ ! -z $assignVarType ]]
    then
        local previousParamNo=$(expr $paramNo - 1)

        if [[ "$assignVarType" == "array" ]]
        then
            # passing array:
            execute="$assignVarName=( \"\${@:$previousParamNo:$assignArrLength}\" )"
            eval "$execute"
            paramNo+=$(expr $assignArrLength - 1)

            unset assignArrLength
        elif [[ "$assignVarType" == "params" ]]
        then
            execute="$assignVarName=( \"\${@:$previousParamNo}\" )"
            eval "$execute"
        elif [[ "$assignVarType" == "reference" ]]
        then
            execute="$assignVarName=\"\$$previousParamNo\""
            eval "$execute"
        elif [[ ! -z "${!previousParamNo}" ]]
        then
            execute="$assignVarName=\"\$$previousParamNo\""
            eval "$execute"
        fi
    fi

    assignVarType="$__capture_type"
    assignVarName="$varName"
    assignArrLength="$__capture_arrLength"
}

Function.CaptureParams() {
    __capture_type="$_type"
    __capture_arrLength="$l"
}

alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\$@\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; '
alias @param='@trapAssign local'
alias @reference='_type=reference @trapAssign local -n'
alias @var='_type=var @param'
alias @params='_type=params @param'
alias @array='_type=array @param'
Interdict answered 4/5, 2015 at 13:42 Comment(0)
M
1

Just to add to the accepted answer, as I found it doesn't work well if the array contents are someting like:

RUN_COMMANDS=(
  "command1 param1... paramN"
  "command2 param1... paramN"
)

In this case, each member of the array gets split, so the array the function sees is equivalent to:

RUN_COMMANDS=(
    "command1"
    "param1"
     ...
    "command2"
    ...
)

To get this case to work, the way I found is to pass the variable name to the function, then use eval:

function () {
    eval 'COMMANDS=( "${'"$1"'[@]}" )'
    for COMMAND in "${COMMANDS[@]}"; do
        echo $COMMAND
    done
}

function RUN_COMMANDS

Just my 2©

Merrythought answered 31/7, 2017 at 20:32 Comment(0)
S
1

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.

Scowl answered 11/7, 2018 at 22:6 Comment(0)
U
1

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.

Uriia answered 4/3, 2019 at 10:6 Comment(2)
Your example is incorrect because it incomplete. Please give full code of script.Sacci
"Snippets" are only for HTML and JavaScript. Use the {} button to code-highlight code in other languages.Spoil
A
1

How to pass regular arrays as parameters by value in bash, via manual parsing and serializing/deserializing

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
Aceto answered 25/1, 2022 at 21:10 Comment(0)
I
0

Requirement: Function to find a string in an array.
This is a slight simplification of DevSolar's solution in that it uses the arguments passed rather than copying them.

myarray=('foobar' 'foxbat')

function isInArray() {
  local item=$1
  shift
  for one in $@; do
    if [ $one = $item ]; then
      return 0   # found
    fi
  done
  return 1       # not found
}

var='foobar'
if isInArray $var ${myarray[@]}; then
  echo "$var found in array"
else
  echo "$var not found in array"
fi 
Inglorious answered 7/4, 2017 at 0:22 Comment(0)
S
-1

You can also create a json file with an array, and then parse that json file with jq

For example:

my-array.json:

{
  "array": ["item1","item2"]
}

script.sh:

ARRAY=$(jq -r '."array"' $1 | tr -d '[],"')

And then call the script like:

script.sh ./path-to-json/my-array.json

You can find more ideas in this similar question: How to pass array as an argument to a function in Bash

Shrike answered 28/10, 2022 at 17:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.