在bash中传递数组作为参数

我怎样才能将一个数组作为parameter passing给一个bash函数?

注意:在Stack Overflow找不到答案之后,我自己贴出了一些粗糙的解决scheme。 它只允许传递一个数组,它是参数列表的最后一个元素。 实际上,它根本不是传递数组,而是通过被调用的函数()重新组合成一个数组元素的列表,但是它对我有用。 如果有人知道更好的方法,请随时在这里添加它。

你可以使用像这样的东西传递多个数组作为参数

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 

会回声:

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

注意:这是我自己张贴的粗略的解决scheme,没有find堆栈溢出的答案。 它只允许传递一个数组,它是参数列表的最后一个元素。 实际上,它根本不是传递数组,而是通过被调用的函数()重新组合成一个数组元素的列表,但是它对我有用。 肯后来肯定了他的解决scheme,但我留在这里“历史”的参考。

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

由TheBonsai改进,谢谢。

评论肯·贝尔森(Ken Bertelson)的解决scheme并回答扬·海蒂(Jan Hettich)

怎么运行的

try_with_local_arys()函数中的takes_ary_as_arg descTable[@] optsTable[@]行发送:

  1. 这实际上是创build了可以被takes_ary_as_arg函数访问的descTableoptsTable数组的takes_ary_as_arg
  2. takes_ary_as_arg()函数接收descTable[@]optsTable[@]作为string,这意味着$1 == descTable[@]$2 == optsTable[@]
  3. takes_ary_as_arg()函数的开始,它使用了${!parameter}语法,它被称为间接引用或者有时被双引用 ,这意味着我们不用$1的值,而是使用$1扩展值的值 ,例如:

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

    同样为$2

  4. 把这个放在argAry1=("${!1}") ,就像写入argAry1=("${descTable[@]}")一样,将argAry1作为一个数组(后面的括号)与扩展的descTable[@]直。 declare没有要求。

注意:值得一提的是,使用这个括号forms的数组初始化是根据IFS或默认为tabnewlinespace的 内部字段分隔符来创build新的数组。 在这种情况下,因为它使用了[@]符号,所以每个元素本身被看作是被引用的(与[*]相反)。

我的保留

BASH ,局部variables作用域是当前函数和从它调用的每个takes_ary_as_arg() ,这意味着takes_ary_as_arg()函数“看到”那些descTable[@]optsTable[@]数组,因此它正在工作(参见上文说明)。

作为这种情况,为什么不自己直接看这些variables呢? 这就像写在那里:

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

参见上面的解释,它只是根据当前的IFS复制descTable[@]数组的值。

综上所述

与往常一样,这本质上是没有价值的东西。

我还想强调上面的Dennis Williamson的评论: 稀疏数组(没有定义所有关键字的数组(不包括所有的关键字) – 不会像预期的那样工作 – 我们将丢失键并“凝聚”数组。

这就是说,我确实看到泛化的价值,因此函数可以在不知道名称的情况下获得数组(或副本):

  • 对于“复制”:这种技术是足够好的,只需要注意,索引(键)已经消失了。
  • 对于真正的副本:我们可以使用一个评估键,例如:

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

然后使用它们来创build副本的循环。 注意:这里! 没有使用它以前的间接/双重评估,而是在数组上下文中返回数组索引(键)。

  • 当然,如果我们要传递descTableoptsTablestring(没有[@] ),我们可以使用数组本身(如在引用中)和eval 。 用于接受数组的通用函数。

这里的基本问题是devise/实现数组的bash开发人员真的搞砸了。 他们认为${array}只是${array[0]}简写,这是一个很糟糕的错误。 特别是当你认为${array[0]}没有意义,并且如果数组types是关联的,则评估为空string。

赋值数组的forms为array=(value1 ... valueN) ,其中value的语法为[subscript]=string ,从而将值直接赋值给数组中的特定索引。 这使得它可以有两种types的数组,数字索引和散列索引(称为bash说法中的关联数组)。 这也使得您可以创build稀疏的数字索引数组。 离开[subscript]=部分是一个数字索引数组的简写,从0的序号索引开始,并在赋值语句中每增加一个新值。

因此, ${array}应该计算整个数组,索引和全部。 它应该评估为赋值语句的反转。 任何第三年的CS专业都应该知道。 在这种情况下,这段代码将完全按照您所期望的那样工作:

 declare -A foo bar foo=${bar} 

然后,按值传递数组到函数并将一个数组分配给另一个将会像其余的shell语法所要求的一样工作。 但是,因为他们没有这样做,赋值运算符=不适用于数组,并且数组不能通过值传递给函数,或者通常使用子shell或输出( echo ${array} )而不用代码来咀嚼通过这一切。

所以,如果这样做是正确的,那么下面的例子将显示在bash中数组的有用性可能会更好:

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

结果输出应该是:

 (first=one second=2 third=3) 

然后,数组可以使用赋值运算符,并通过值传递给函数,甚至其他shell脚本。 通过输出到文件轻松存储,并轻松从文件加载到脚本中。

 declare -A foo read foo <file 

唉,我们已经被一个最高级的bash开发团队辜负了。

因此,为了将一个数组传递给函数,实际上只有一个选项,那就是使用nameref特性:

 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 

将导致以下输出:

 indexes: foo zoom values: bar fast 

由于这是通过引用传递的,所以也可以分配给函数中的数组。 是的,被引用的数组必须具有全局范围,但考虑到这是shell脚本,这不应该太大。 要通过值传递一个关联或稀疏索引数组到一个函数,需要将所有的索引和值抛出到参数列表(不是很有用,如果它是一个大数组),就像这样的单个string:

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

然后在函数内部编写一堆代码来重组数组。

DevSolar的答案有一点我不明白(也许他有一个具体的理由这样做,但我想不出一个):他从位置参数元素元素迭代设置数组。

一个更容易的approuch将是

 called_function() { ... # do everything like shown by DevSolar ... # now get a copy of the positional parameters local_array=("$@") ... } 
 function aecho { set "$1[$2]" echo "${!1}" } 

 $ foo=(dog cat bird) $ aecho foo 1 cat 

传递几个数组作为参数的简单方法是使用字符分隔的string。 你可以这样调用你的脚本:

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

然后,你可以像这样在你的代码中提取它:

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

这样,您实际上可以将多个数组作为parameter passing,而不必是最后一个参数。

即使有空格,这个也能工作:

 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[@]}" 

通过一些技巧,你可以将命名的参数和数组一起传递给函数。

我开发的方法允许您访问传递给像这样的函数的参数:

 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" 

换句话说,不仅可以通过名字来调用你的参数(这可以构成一个更可读的核心),你实际上可以传递数组(并且引用variables – 这个特性只能在bash 4.3中起作用)! 另外,映射的variables都在本地范围内,就像$ 1(和其他)一样。

使这个工作的代码是相当轻的,并在bash 3和bash 4(这是我testing过的唯一版本)。 如果你对这样的技巧感兴趣,可以用bash进行更好更简单的开发,可以看看我的Bash Infinity Framework ,下面的代码就是为这个目的而开发的。

 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' 

只是添加到被接受的答案,因为我发现如果数组的内容是这样的:

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

在这种情况下,数组的每个成员都被分割,所以函数看到的数组相当于:

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

为了让这个例子起作用,我发现的方法是把variables名传给函数,然后使用eval:

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

只是我的2©

要求 :函数在数组中查找string。
这只是DevSolar解决scheme的一个小小的简化,它使用传递的参数而不是复制它们。

 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