Reputation: 1916
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
Upvotes: 59
Views: 118842
Reputation: 581
Just easier to source another bash script containing the array. Especially if the array is associative, because you cannot pass associative arrays.
i.e.
array.sh
#!/bin/bash
declare -A sounds=([dog]="Bark" [wolf]="Howl")
myscript.sh
#!/bin/bash
source array.sh
echo ${sounds[wolf]}
cli
$ bash myscript.sh
$ Howl
Upvotes: 0
Reputation: 51
The phrase "I would like to pass an array to my script" is too imprecise. You want to be more concrete what you intend to do.
There are two main use cases:
a) You want to pass the contents of an existing array as input to a function (or another script), but you don't want the function to modify the array in any way. In C, you'd call this "by value".
b) You want the function to build a new or modify an existing array and return that array as result. Unfortunately, shells can only return exit codes. An elegant way in Bash 4.2 (4.3) and newer is to pass the name of the array as argument and then declare a local name as a reference. In C, you'd call this "by reference".
The suggested solution to use case a) in this thread is to pass all the array elements as the last arguments of the function call. I don't like that approach, it is considered complex and error-prone. There are at least two other alternatives:
Convert the array to a string, pass the string, and rebuild a local copy of the array in the function. You could write your own serialization and deserialization functions. PHP uses explode() and implode() for that purpose and it is not too hard to write bash pendants.
Pass the array per reference and just make sure that you treat it as read-only in the function. This is what I've done when I wrote my own implode() function.
The suggested solution to use case b) is documented here. Just don't get confused by the bash manual which states that "the nameref attribute cannot be applied to array variables" or prominent Bash pages which claim that "you can't pass the name of an array to a function and let the function use it"--it is actually perfectly fine to pass array names as function arguments, the manual warning really refers to arrays of name references (see here).
When using array variables per reference, you'll sooner or later run into an issue with circular name references which do not exist but are really just implementation flaws (to avoid the term unfixed bugs, see here).
Upvotes: -1
Reputation: 11
If you're okay with the array being the last variable passed and only able to pass one array, then the suggested answer is the absolute best answer; however, it implies a rule that must be followed... that being the array must be last and there is an understood placement of each value passed.
For a completely dynamic way to pass variable/value pairs, with the least amount of rules obeyed, this method seems to work well for me. There are two rules:
This means that to define choices array and validBool array, it would look like:
script.sh --varName1 value1 \
--varName2 value2 \
--ARRAY 5 \
--choices 1 2 3 4 5 \
--varName3 value3 \
--ARRAY 2 \
--validBool true false \
--varName4 value4
This yields:
varName1="value1";
varName2="value2";
choices=("1", "2", "3", "4", "5");
varName3="value3";
validBool=("true", "false");
varName4="value4";
As you can see, you can easily pass any type of variable/value pair via this code snippet. What's great is you can store this code snippet in a file "GetArgValues.sh" and "source" include it as needed. NOTE: You can't make it a function because you will have lost the arguments that were passed to the script that invokes it.
Here's the code:
# sample:
# script.sh --varName1 value1 --varName2 value2 --ARRAY 5 --choices 1 2 3 4 5 --varName3 value3 --ARRAY 2 --validBool true false --varName4 value4
# author: Edwin Handschuh
_argCount=$#;
while [[ ${_argCount} -gt 0 ]]; do
if [[ ${_argCount} -ne 0 ]]; then
if [[ $# -gt 1 ]]; then
_varName="${1}";
if [[ "${_varName:0:2}" != "--" ]]; then
_rs=16;
_failureMsg="Invalid name/value pair specified. Name must begin with a '--' characters. Name found '${_varName}'. Aborting script with result status=${_rs}";
echo "$_failureMsg";
kill -INT $$;
fi;
_varName="${_varName:2:${#_varName}-2}";
shift;
_varValue="${1}";
shift;
declare -g ${_varName}="${_varValue}";
if [[ "${_varName}" = "ARRAY" ]]; then
if [[ $# -gt 1 ]]; then
_varName="${1}";
if [[ "${_varName:0:2}" != "--" ]]; then
_rs=16;
_failureMsg="Invalid name/value pair specified. Name must begin with a '--' characters. Name found '${_varName}'. Aborting script with result status=${_rs}";
echo "$_failureMsg";
kill -INT $$;
fi;
_varName="${_varName:2:${#_varName}-2}";
declare -ga ${_varName};
_arrayCounter=$ARRAY;
while [[ $ARRAY -gt 0 ]]; do
shift;
# let "_index = _arrayCounter - ARRAY";
((_index = _arrayCounter - ARRAY));
declare -ga "${_varName}[${_index}]"="${1}";
((ARRAY--));
done;
shift;
fi;
fi;
fi;
fi;
_argCount=$#;
done
# hey, we're done!
Upvotes: -1
Reputation: 70742
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.
#!/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")
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")
#!/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.
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")
Upvotes: 1
Reputation: 70742
And passing array as argument to remote script by using alternate separator
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."
local IFS=
I ensure IFS
varialbe won't be modified in environmentlocal -n
is a nameref for binding variables.${src[*]}
will merge all elements of array to one string by using 1st character of $IFS
\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.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!! ;-)
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?"
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 myscript
, I see:{ "height": "5.5 feet", "weight": "142 pounds", "phrase": "Let's go!" }
:-)
Upvotes: 3
Reputation: 19
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}
Upvotes: 1
Reputation: 322
The best solution that I'm found here
f() {
declare -a argAry1=("${!1}")
echo "${argAry1[@]}"
# ...
}
arr1=(
"a"
"b"
)
f arr1[@] arr2[@] arg2
Upvotes: 0
Reputation: 11519
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.
Upvotes: 15
Reputation: 331
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
Upvotes: 2
Reputation: 7
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.
Upvotes: -4
Reputation: 784958
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
Upvotes: 20
Reputation: 246754
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
Upvotes: 43