可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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
回答2:
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=(\"${@}\")
}
Improved by TheBonsai, thanks.
回答3:
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:
- This is actually creates a copy of the
descTable
and optsTable
arrays which are accessible to the takes_ary_as_arg
function.
takes_ary_as_arg()
function receives descTable[@]
and optsTable[@]
as strings, that means $1 == descTable[@]
and $2 == optsTable[@]
.
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
.
- 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:
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.
回答4:
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.
回答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=(\"$@\")
...
}
回答6:
function aecho {
set \"$1[$2]\"
echo \"${!1}\"
}
Example
$ foo=(dog cat bird)
$ aecho foo 1
cat
回答7:
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.
回答8:
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[@]}\"
回答9:
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\'
回答10:
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©
回答11:
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.
回答12:
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