52

It is surprising me that I do not find the answer after 1 hour search for this. I would like to pass an array to my script like this:

test.sh argument1 array argument2

I DO NOT want to put this in another bash script like following:

array=(a b c)
for i in "${array[@]}"
do
  test.sh argument1 $i argument2
done
codeforester
  • 39,467
  • 16
  • 112
  • 140
zhihong
  • 1,808
  • 2
  • 24
  • 34

9 Answers9

40

Bash arrays are not "first class values" -- you can't pass them around like one "thing".

Assuming test.sh is a bash script, I would do

#!/bin/bash
arg1=$1; shift
array=( "$@" )
last_idx=$(( ${#array[@]} - 1 ))
arg2=${array[$last_idx]}
unset array[$last_idx]

echo "arg1=$arg1"
echo "arg2=$arg2"
echo "array contains:"
printf "%s\n" "${array[@]}"

And invoke it like

test.sh argument1 "${array[@]}" argument2
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • Great answer. I know this doesn't conform to OP's requirement of `test.sh argument1 array argument2` but if the invocation was changed to `test.sh argument1 argument2 array` (the array being the last), it'd be less work. – doubleDown Jun 21 '13 at 10:58
  • 4
    Good answer. But technically it is not passing array to a script. What if OP needs to pass 2 array variables to this script like `test.sh argument1 array1 array2` – anubhava Jun 21 '13 at 11:11
  • 2
    Note that you need to declare the array before calling the script. For the OP, this would mean `$ array=(a b c)` and then `test.sh argument1 "${array[@]}" argument2` . It might only be me who had to think about that a bit, but I'm commenting in the hope that it will help someone. – bballdave025 Apr 06 '19 at 22:14
  • @anubhava Finally, ten year further, I published [another correct answer](https://stackoverflow.com/a/75764948/1765658), I think. – F. Hauri - Give Up GitHub Mar 17 '23 at 07:52
18

Have your script arrArg.sh like this:

#!/bin/bash

arg1="$1"
arg2=("${!2}")
arg3="$3"
arg4=("${!4}")

echo "arg1=$arg1"
echo "arg2 array=${arg2[@]}"
echo "arg2 #elem=${#arg2[@]}"
echo "arg3=$arg3"
echo "arg4 array=${arg4[@]}"
echo "arg4 #elem=${#arg4[@]}"

Now setup your arrays like this in a shell:

arr=(ab 'x y' 123)
arr2=(a1 'a a' bb cc 'it is one')

And pass arguments like this:

. ./arrArg.sh "foo" "arr[@]" "bar" "arr2[@]"

Above script will print:

arg1=foo
arg2 array=ab x y 123
arg2 #elem=3
arg3=bar
arg4 array=a1 a a bb cc it is one
arg4 #elem=5

Note: It might appear weird that I am executing script using . ./script syntax. Note that this is for executing commands of the script in the current shell environment.

Q. Why current shell environment and why not a sub shell?
A. Because bash doesn't export array variables to child processes as documented here by bash author himself

anubhava
  • 761,203
  • 64
  • 569
  • 643
9

If values have spaces (and as a general rule) I vote for glenn jackman's answer, but I'd simplify it by passing the array as the last argument. After all, it seems that you can't have multiple array arguments, unless you do some complicated logic to detect their boundaries.

So I'd do:

ARRAY=("the first" "the second" "the third")
test.sh argument1 argument2 "${ARRAY[@]}"

which is the same as:

test.sh argument1 argument2 "the first" "the second" "the third"

And in test.sh do:

ARG1="$1"; shift
ARG2="$1"; shift
ARRAY=("$@")

If values have no spaces (i.e. they are urls, identifiers, numbers, etc) here's a simpler alternative. This way you can actually have multiple array arguments and it's easy to mix them with normal arguments:

ARRAY1=(one two three)
ARRAY2=(four five)
test.sh argument1 "${ARRAY1[*]}" argument3 "${ARRAY2[*]}" 

which is the same as:

test.sh argument1 "one two three" argument3 "four five"

And in test.sh you do:

ARG1="$1"
ARRAY1=($2) # Note we don't use quotes now
ARG3="$3"
ARRAY2=($4)

I hope this helps. I wrote this to help (you and me) understand how arrays work, and how * an @ work with arrays.

Ferran Maylinch
  • 10,919
  • 16
  • 85
  • 100
4

Passing array as argument to script by using alternate separator

And passing array as argument to remote script by using alternate separator

Practical sample:

Choosing bell character: ascii 0x07 used by C sequence: \a.

Little preparation: Having a function to prepare variables:

mergeArray() {
    local IFS=$'\a'
    local -n src=$1 tgt=$2
    tgt=${src[*]}
}

Then the script could look like:

#!/bin/bash

arg1="$1"
IFS=$'\a' read -ra arg2 <<<"$2"
arg3="$3"

declare -p arg{1,2,3}

In practice:

var1=Hello var2=(foo bar baz) var3=world.
mergeArray var2 mvar2
bash script "$var1" "$mvar2" "$var3"

must output:

declare -- arg1="Hello"
declare -a arg2=([0]="foo" [1]="bar" [2]="baz")
declare -- arg3="world."

Explanations

  • by using local IFS= I ensure IFS varialbe won't be modified in environment
  • local -n is a nameref for binding variables.
  • ${src[*]} will merge all elements of array to one string by using 1st character of $IFS
  • Of course, I use \a for this, this could replaced (on both side) by any other single byte non null character (ascii) from \1 to \377, while choosed character is not used in your array.

Installation and remote execution

The function mergeArray could be installed into your .bashrc file or into a separated file to be sourced before you run your script.

Once done, the script himself could even be located remotely. then run by same command:

ssh user@remote /path/to/script  "$var1" "$mvar2" "$var3"

But for more robust remote execution I prefer:

myArray=("foo bar baz" 'alice bob charlie' 'strawberry raspberry')
mergeArray myArray mergedArray
var1="Hello world!"
ssh user@remote /bin/bash -s <<eoRemote
    /path/to/script  "$var1" "$mergedArray" "This seem nice, isnt't it?"
eoRemote

From my remote host, I will read:

declare -- arg1="Hello world!"
declare -a arg2=([0]="foo bar baz" [1]="alice bob charlie" [2]="strawberry raspberry")
declare -- arg3="This seem nice, isnt't it?"

( Notice the mixing of single quotes and double quotes!! ;-)

Passing Associative arrays as argument to remote bash via ssh

Things become a little stronger!

mergeArray () { 
    local -n _srce=${1?Source var missing.} _trgt=${2?Target var missing.}
    local  _tvar
    IFS=\  read _ _tvar _ <<< "${_srce@A}"
    case ${_tvar#-} in 
        *a*)  local IFS=$'\a'; _trgt=${_srce[*]} ;;
        *A*)  _trgt=''
              for _tvar in "${!_srce[@]}" ;do
                  printf -v _tvar '%s\a%s\a' "$_tvar" "${_srce[$_tvar]}"
                  _trgt+="$_tvar"
              done
              _trgt=${_trgt%$'\a'} ;;
        *  ) printf >&2 '%s ERROR: Variable "%s" is not an array.\n' \
                 $FUNCNAME "$1"
             return 1 ;;
    esac
}

Then parsing args in script will become:

#!/bin/bash

arg1=${1}

IFS=$'\a' read -ra arg2 <<<"$2"

IFS=$'\a' read -ra _tmpvar <<<"$3"
printf -v _tmpvar '[%s]="%s" ' "${_tmpvar[@]//\"/\\\"}"
declare -A arg3="($_tmpvar)"

arg4=$4

declare -p arg{1,2,3,4}

In action:

var1="Hello world!"
myArray=("foo bar baz" 'alice bob charlie' 'strawberry raspberry')
declare -A myAArray='([Full name]="John Doo" [Birth date]="1970/01/02 12:34:56"
    [Status]="Maried" [Title]="Chief")'
mergeArray myArray mergedArray
mergeArray myAArray mergedAArray
ssh user@remote /bin/bash -s <<eoRemote
    /path/to/script  "$var1" "$mergedArray" "$mergedAArray" "Still seem nice, isn't it?"
eoRemote
declare -- arg1="Hello world!"
declare -a arg2=([0]="foo bar baz" [1]="alice bob charlie" [2]="strawberry raspberry")
declare -A arg3=(["Birth date"]="1970/01/02 12:34:56" [Title]="Chief" [Status]="Maried" ["Full name"]="John Doo" )
declare -- arg4="Sill seem nice, isn't it?"

Passing complex variables as arguments over ssh

And for holding double-quotes in addition to already supported single-guotes, spaces and others, remplace $varNAme by ${varName//\"/\\\"}:

var1="Hello world!"

myArray=("foo bar baz" 'alice bob charlie' 'strawberry raspberry')

declare -A myAArray='([Full name]="John Doo" [Birth date]="1970/01/02 12:34:56"
    [Status]="Maried")'
myAArray[Title]="Chief's pain sufferer"
myAArray[datas]='{ "height": "5.5 feet", "weight":"142 pounds",'
myAArray[datas]+=$' "phrase": "Let\'s go!" }'

mergeArray myArray mergedArray
mergeArray myAArray mergedAArray

ssh user@remote /bin/bash -s <<eoRemote
    /path/to/script  "${var1//\"/\\\"}" "${mergedArray//\"/\\\"}" \
        "${mergedAArray//\"/\\\"}" "This still seem nice, isn't it?"
eoRemote
declare -- arg1="Hello world!"
declare -a arg2=([0]="foo bar baz" [1]="alice bob charlie" [2]="strawberry raspberry")
declare -A arg3=([Title]="Chief's pain sufferer" [Status]="Maried" ["Birth date"]="1970/01/02 12:34:56" ["Full name"]="John Doo" [datas]="{ \"height\": \"5.5 feet\", \"weight\":\"142 pounds\", \"phrase\": \"Let's go!\" }" )
declare -- arg4="This still seem nice, isn't it?"

Or after some foldering:

declare -- arg1="Hello world!"

declare -a arg2=([0]="foo bar baz" [1]="alice bob charlie"
                 [2]="strawberry raspberry" )

declare -A arg3=([Title]="Chief's pain sufferer" [Status]="Maried"
                 ["Birth date"]="1970/01/02 12:34:56" ["Full name"]="John Doo"
                 [datas]="{ \"height\": \"5.5 feet\", \"weight\":\"142 pounds\",
                            \"phrase\": \"Let's go!\" }" )

declare -- arg4="This still seem nice, isn't it?"

( By adding: jq <<<${arg3[datas]} at end of my script, I see:

{
  "height": "5.5 feet",
  "weight": "142 pounds",
  "phrase": "Let's go!"
}

:-)

F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
2

You can write your array to a file, then source the file in your script. e.g.:

array.sh

array=(a b c)

test.sh

source $2
...

Run the test.sh script:

./test.sh argument1 array.sh argument3
Dd H
  • 331
  • 1
  • 3
  • 9
2

if the length of your array is not too long, you can convert the array to a string which joining with ','

file1.sh

s=${1}
arr=(`echo ${s} | tr ',' ' '`)  # then we can convert str to arr

file2.sh

a="1,2,3,4"
sh file1.sh ${a} 
x Star
  • 21
  • 1
2

Run bash script with arrays as argument, using getopts

You said:

It is surprising me that I do not find the answer after 1 hour search for this

Then surprisingly, 10 years after you asked this, no one did give this answer...

Re-reading my previous answer, the title of this question and other answers, I realized that for making a script waiting for an array, I'v used another way in Parallelize stream processing using bash + sed

Using getopts, you could use any flag repetitively on command line:

./sparallel.sh -t dhclient -t dnsmasq -t rsyncd -b '\b' -a '[' "./filter-<RE>.sh"

Where the repititive use of -t flag will build and populate an array.

Sample

#!/bin/bash
usage() { echo "Usage: $0 [-a arg] [-aarg]...";}
Array=()
while getopts "ha:" opt; do
    case $opt in
        h ) usage; exit ;;
        a ) Array+=("$OPTARG");;
        ... ) : Some other flags if needed;;
        * ) echo Wrong argument.;exit 1 ;;
    esac
done
shift $((OPTIND-1))

printf 'There are now two arrays: "$@" (%d) and "$Array" (%d)\n' $# ${#Array[@]}
echo "${*@A}"
echo "${Array[@]@A}"

Then:

./argArrayTest.sh -a 'Foo bar' -a test -a{a..b}{1,2} Furter args: 'Hello world.'
There are now two arrays: "$@" (3) and "$Array" (6)
set -- 'Furter' 'args:' 'Hello world.'
declare -a Array=([0]="Foo bar" [1]="test" [2]="a1" [3]="a2" [4]="b1" [5]="b2")

With your test case:

array=(a b c)
./argArrayTest.sh "${array[@]/#/-a}" argument1 argument2
There are now two arrays: "$@" (2) and "$Array" (3)
set -- 'argument1' 'argument2'
declare -a Array=([0]="a" [1]="b" [2]="c")

Same script, with associative arrays:

#!/bin/bash
usage() { echo "Usage: $0 [-a arg] [-aarg]...";}
Array=()
declare -A AArray='()'
while getopts "ha:A:" opt; do
    case $opt in
        h ) usage; exit ;;
        a ) Array+=("$OPTARG");;
        A ) fld="${OPTARG%%=*}" val="${OPTARG#$fld}" AArray["$fld"]="${val#=}";;
        ... ) : Some other flags if needed;;
        * ) echo Wrong argument.;exit 1 ;;
    esac
done
shift $((OPTIND-1))

printf 'You now have three arrays: "$@"(%d), "$Array"(%d) and "$AArray"(%d)\n' \
       $# ${#Array[@]} ${#AArray[@]}
echo "${*@A}"
declare -p {A,}Array

Usage:

./argArrayTest.sh -a 'Foo bar' -a test -a{a..b}\ {1,2} -A test=Foo\ bar -A flag \
    -AString==Complex$'\n'"line bounded by =" -Aequal== Furter args: 'Hello world.'
You now have three arrays: "$@"(3), "$Array"(6) and "$AArray"(4)
set -- 'Furter' 'args:' 'Hello world.'
declare -A AArray=([flag]="" [equal]="=" [String]=$'=Complex\nline bounded by =' [test]="Foo bar" )
declare -a Array=([0]="Foo bar" [1]="test" [2]="a 1" [3]="a 2" [4]="b 1" [5]="b 2")

More complete sample, permitting += syntax to concatenate strings or add to integers, on my website.

Into a function

Answering DevSolar's comment on some kind of duplicate, here is same minimal function you could test directly into any terminal:

argAryTst() { 
    local OPT{ARG,IND,ERR} opt Array=()
    local -A AArray='()'
    while getopts "a:A:" opt; do
        case $opt in
            a) Array+=("$OPTARG") ;;
            A) fld="${OPTARG%%=*}" val="${OPTARG#$fld}" AArray["$fld"]="${val#=}" ;;
        esac
    done
    shift $((OPTIND-1))
    printf 'You now have three arrays: "$@" (%d), "$%s" (%d) and "$%s" (%d)\n' \
        $# Array ${#Array[@]} AArray ${#AArray[@]}
    echo "${*@A}"
    declare -p {A,}Array
}
argAryTst -a foo -A from=Alice -A to=Bob -a bar\ baz Hello world.
You now have three arrays: "$@" (2), "$Array" (2) and "$AArray" (2)
set -- 'Hello' 'world.'
declare -A AArray=([from]="Alice" [to]="Bob" )
declare -a Array=([0]="foo" [1]="bar baz")
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
1

The best solution that I'm found here

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

arr1=(
    "a"
    "b"
)
f arr1[@] arr2[@] arg2
nextloop
  • 166
  • 9
-4

If this is your command:

test.sh argument1 ${array[*]} argument2

You can read the array into test.sh like this:

arg1=$1
arg2=${2[*]}
arg3=$3

It will complain at you ("bad substitution"), but will work.