如何将数组作为参数传递给bash函数?
注意:在Stack Overflow上找不到答案后,我自己发布了一些粗略的解决方案。它只允许传递一个数组,它是参数列表的最后一个元素。实际上,它根本没有传递数组,而是传递了它的元素列表,这些元素通过called_function()重新组合成一个数组,但它对我有用。如果有人知道更好的方法,请随意添加。
答案 0 :(得分:205)
您可以使用以下内容传递多个数组作为参数:
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
答案 1 :(得分:83)
注意:这是我自己发布的有点粗糙的解决方案,之后在Stack Overflow上找不到答案。它只允许传递一个数组,它是参数列表的最后一个元素。实际上,它根本没有传递数组,而是传递了它的元素列表,这些元素通过called_function()重新组合成一个数组,但它对我有用。稍后Ken发布了他的解决方案,但我在这里保留了他的“历史性”参考资料。
calling_function()
{
variable="a"
array=( "x", "y", "z" )
called_function "${variable}" "${array[@]}"
}
called_function()
{
local_variable="${1}"
shift
local_array=("${@}")
}
由TheBonsai改进,谢谢。
答案 2 :(得分:36)
评论Ken Bertelson解决方案并回答Jan Hettich:
takes_ary_as_arg descTable[@] optsTable[@]
函数中的try_with_local_arys()
行发送:
descTable
和optsTable
数组的副本,takes_ary_as_arg
函数可以访问这些数组。 takes_ary_as_arg()
函数接收descTable[@]
和optsTable[@]
字符串,表示$1 == descTable[@]
和$2 == optsTable[@]
。在takes_ary_as_arg()
函数的开头,它使用${!parameter}
语法,称为indirect reference or sometimes double referenced,这意味着而不是使用$1
的值,我们使用$1
的扩展值的值,例如:
baba=booba
variable=baba
echo ${variable} # baba
echo ${!variable} # booba
同样适用于$2
。
argAry1=("${!1}")
中,使用展开的argAry1
创建=
数组(descTable[@]
后面的括号),就像直接写argAry1=("${descTable[@]}")
一样。
declare
没有必要。 NB:值得一提的是,使用此括号表单的数组初始化根据IFS
或内部字段分隔符初始化新数组标签,换行符和空间。在这种情况下,因为它使用了[@]
符号,所以每个元素都被看作是被引用的(与[*]
相反)。
在BASH
中,局部变量范围是当前函数以及从中调用的每个子函数,这转换为takes_ary_as_arg()
函数“看到”那些descTable[@]
和{{1数组,因此它正在工作(参见上面的解释)。
既然如此,为什么不直接看看那些变量呢?这就像写在那里:
optsTable[@]
参见上面的解释,它只根据当前argAry1=("${descTable[@]}")
复制descTable[@]
数组的值。
这实际上是没有任何价值 - 像往常一样。
我还想强调丹尼斯威廉姆森上面的评论:稀疏数组(没有所有键定义的数组 - 其中带有“漏洞”)将无法按预期工作 - 我们会松开键和“浓缩“阵列。
话虽如此,我确实看到了泛化的价值,因此函数可以在不知道名称的情况下得到数组(或副本):
用于真实副本: 我们可以使用eval作为键,例如:
IFS
然后使用它们创建一个副本。
注意:此处eval local keys=(\${!$1})
未使用它以前的间接/双重评估,而是在数组上下文中返回数组索引(键)。
!
和descTable
字符串(没有optsTable
),我们可以使用数组本身(如引用中所示){{1 }}。对于接受数组的通用函数。答案 3 :(得分:19)
这里的基本问题是设计/实现数组的bash开发人员真的搞砸了小狗。他们认为${array}
只是${array[0]}
的简称,这是一个错误的错误。特别是当您认为${array[0]}
没有意义时,如果数组类型是关联的,则计算空字符串。
分配数组采用array=(value1 ... valueN)
形式,其中value的语法为[subscript]=string
,从而直接为数组中的特定索引赋值。这使得它可以有两种类型的数组,数字索引和哈希索引(在bash用语中称为关联数组)。它还使您可以创建稀疏的数字索引数组。离开[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脚本,这不应该是一个大问题。要按值将关联或稀疏索引数组传递给函数,需要将所有索引和值抛出到参数列表中(如果它是一个大数组,则不太有用)作为单个字符串,如下所示:
funky "${!array[*]}" "${array[*]}"
然后在函数内部写一堆代码来重新组装数组。
答案 4 :(得分:5)
DevSolar的答案有一点我不明白(也许他有一个特定的理由这样做,但我想不到一个):他从元素迭代的位置参数元素设置数组。
更容易approuch
called_function()
{
...
# do everything like shown by DevSolar
...
# now get a copy of the positional parameters
local_array=("$@")
...
}
答案 5 :(得分:3)
function aecho {
set "$1[$2]"
echo "${!1}"
}
实施例
$ foo=(dog cat bird)
$ aecho foo 1
cat
答案 6 :(得分:2)
将多个数组作为参数传递的简单方法是使用以字符分隔的字符串。你可以这样调用你的脚本:
./myScript.sh "value1;value2;value3" "somethingElse" "value4;value5" "anotherOne"
然后,您可以在代码中提取它,如下所示:
myArray=$1
IFS=';' read -a myArray <<< "$myArray"
myOtherArray=$3
IFS=';' read -a myOtherArray <<< "$myOtherArray"
这样,您实际上可以将多个数组作为参数传递,并且它不必是最后一个参数。
答案 7 :(得分:1)
这个甚至可以用空格:
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[@]}"
答案 8 :(得分:1)
通过一些技巧,您可以将命名参数与数组一起传递给函数。
我开发的方法允许您访问传递给函数的参数,如下所示:
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"
换句话说,您不仅可以通过名称调用参数(这样可以构成更具可读性的核心),您实际上可以传递数组(以及对变量的引用 - 这个功能仅适用于bash 4.3)!另外,映射变量都在本地范围内,就像$ 1(和其他)一样。
使这项工作的代码非常轻松,并且在bash 3和bash 4中都有效(这些是我用它测试过的唯一版本)。如果你对这样的更多技巧感兴趣,那么使用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'
答案 9 :(得分:1)
只是添加到接受的答案中,因为我发现如果数组内容有点像这样的话,它就不能正常工作:
RUN_COMMANDS=(
"command1 param1... paramN"
"command2 param1... paramN"
)
在这种情况下,数组的每个成员都被拆分,因此函数看到的数组相当于:
RUN_COMMANDS=(
"command1"
"param1"
...
"command2"
...
)
为了使这种情况起作用,我找到的方法是将变量名称传递给函数,然后使用eval:
function () {
eval 'COMMANDS=( "${'"$1"'[@]}" )'
for COMMAND in "${COMMANDS[@]}"; do
echo $COMMAND
done
}
function RUN_COMMANDS
只是我的2©
答案 10 :(得分:1)
丑陋的是,这是一种变通方法,只要您没有显式传递数组,而是传递与数组相对应的变量,便可以使用:
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
我确定有人可以提出一个更清晰的想法,但是我发现这比将数组作为"{array[@]"}
传递,然后使用{{1}内部访问它是一个更好的解决方案。 }。当还有其他position / array_inside=("$@")
参数时,这变得很复杂。在这些情况下,我必须先确定然后使用getopts
和数组元素删除的某种组合来删除与数组不相关的参数。
一个纯粹主义者的观点很可能将此方法视为对语言的侵犯,但从务实的角度来说,这种方法使我免于很多痛苦。在相关主题上,我还使用shift
将内部构造的数组分配给根据传递给函数的参数eval
命名的变量:
target_varname
希望这对某人有帮助。
答案 11 :(得分:0)
要求:在数组中查找字符串的函数 这是DevSolar解决方案的略微简化,因为它使用传递的参数而不是复制它们。
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
答案 12 :(得分:0)
我的简短答案是:
function display_two_array {
local arr1=$1
local arr2=$2
for i in $arr1
do
"arrary1: $i"
done
for i in $arr2
do
"arrary2: $i"
done
}
test_array=(1 2 3 4 5)
test_array2=(7 8 9 10 11)
display_two_array "${test_array[*]}" "${test_array2[*]}"
${test_array[*]}
和${test_array2[*]}
应该用“”包围,否则会失败。