如何将数组作为参数传递给bash函数?
注意:在此处没有关于Stack Overflow的答案之后,我自己发布了一些粗略的解决方案。它只允许传递一个数组,它是参数列表的最后一个元素。实际上,它根本不是传递数组,而是传递其元素的列表,这些元素可以通过named_function()重新组装成数组,但是它对我有用。如果有人知道更好的方法,请随时在此处添加。
如何将数组作为参数传递给bash函数?
注意:在此处没有关于Stack Overflow的答案之后,我自己发布了一些粗略的解决方案。它只允许传递一个数组,它是参数列表的最后一个元素。实际上,它根本不是传递数组,而是传递其元素的列表,这些元素可以通过named_function()重新组装成数组,但是它对我有用。如果有人知道更好的方法,请随时在此处添加。
Answers:
您可以使用以下方式将多个数组作为参数传递:
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
编辑/注释:(来自下面的评论)
descTable
和optsTable
作为名称传递并在函数中扩展。因此没有$
,当作为参数给出时不需要。descTable
等定义local
,因为本地对其调用的函数可见。!
在${!1}
扩展ARG 1可变。declare -a
只是使索引数组变得明确,并不是严格必要的。注意:这是我在堆栈溢出问题上找不到答案后发布的解决方案。它只允许传递一个数组,它是参数列表的最后一个元素。实际上,它根本不是传递数组,而是传递其元素的列表,这些元素可以通过named_function()重新组装成数组,但是它对我有用。后来,肯(Ken)发表了他的解决方案,但我在这里保留了“历史”参考。
calling_function()
{
variable="a"
array=( "x", "y", "z" )
called_function "${variable}" "${array[@]}"
}
called_function()
{
local_variable="${1}"
shift
local_array=("${@}")
}
由TheBonsai改进,谢谢。
called_function "${#array[@]}" "${array[@]}" "${#array2[@]}" "${array2[@]}"
等等……仍然有一些明显的限制,但实际上,以一种语言支持的方式解决该问题要好得多,而不是试图使该语言以其他语言习惯的方式工作。
评论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}
语法,称为间接引用或有时称为双重引用,这意味着我们将使用的扩展值的值,而不是$1
使用的值$1
,例如:
baba=booba
variable=baba
echo ${variable} # baba
echo ${!variable} # booba
同样地$2
。
argAry1=("${!1}")
会创建argAry1
一个数组,就像直接在此处编写一样。在那里不是必需的。=
descTable[@]
argAry1=("${descTable[@]}")
declare
注意:值得一提的是,使用此括号形式的数组初始化根据IFS
或内部字段分隔符(默认情况下为tab,newline和space)来初始化新数组。在这种情况下,由于它使用了[@]
表示法,因此每个元素本身都被视为自己被引用了(与[*]
)。
在中BASH
,局部变量作用域是当前函数以及从中调用的每个子函数,这转化为以下事实:takes_ary_as_arg()
函数“看到”了这些函数,descTable[@]
并且optsTable[@]
数组数组,因此该函数起作用(请参见上面的说明)。
在这种情况下,为什么不直接查看这些变量呢?就像在这里写:
argAry1=("${descTable[@]}")
参见上面的说明,该说明仅descTable[@]
根据当前值复制数组的值IFS
。
从本质上讲,这与往常一样没有价值传递。
我也想强调丹尼斯·威廉姆森的上述评论:稀疏数组(没有定义所有键的数组-其中带有“孔”)将无法按预期工作-我们将松开键并“压缩”该数组。
话虽如此,我确实看到了泛化的价值,因此函数可以在不知道名称的情况下获取数组(或副本):
对于真实副本:我们可以使用eval作为密钥,例如:
eval local keys=(\${!$1})
然后循环使用它们创建副本。注意:这里!
不使用它以前的间接/双重计算,而是在数组上下文中返回数组索引(键)。
descTable
和optsTable
字符串(不带[@]
),我们可以将数组本身(如通过引用使用)与eval
。用于接受数组的通用函数。Array1
,然后用调用Array2
,传递数组名称变得很方便。
这里的基本问题是设计/实现数组的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
las,我们已经被一个本来就不算什么的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[*]}"
然后在函数内部编写一堆代码以重新组装数组。
local -n
与接受的答案相比,使用的解决方案更好,最新。该解决方案也适用于任何类型的变量。此答案中列出的示例可以简化为local -n ARR=${1}
。但是/ 的-n
选项仅在Bash 4.3及更高版本中可用。local
declare
funky ARR
)相同,则shell将发出警告circular name reference
,因为基本上该函数会尝试这样做local -n ARR=ARR
。关于这个话题的好讨论。
DevSolar的回答有一点我不明白(也许他有一个特定的理由,但我想不到):他从位置参数逐个元素地,迭代地设置数组。
一个更简单的方法是
called_function()
{
...
# do everything like shown by DevSolar
...
# now get a copy of the positional parameters
local_array=("$@")
...
}
将多个数组作为参数传递的一种简单方法是使用字符分隔的字符串。您可以这样调用脚本:
./myScript.sh "value1;value2;value3" "somethingElse" "value4;value5" "anotherOne"
然后,您可以像这样将其提取到代码中:
myArray=$1
IFS=';' read -a myArray <<< "$myArray"
myOtherArray=$3
IFS=';' read -a myOtherArray <<< "$myOtherArray"
这样,您实际上可以将多个数组作为参数传递,而不必是最后一个参数。
即使在有空格的情况下,这也可以使用:
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"
换句话说,不仅可以通过名称来调用参数(这构成了更易读的内核),而且实际上可以传递数组(以及对变量的引用-该功能仅在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'
只是添加到已接受的答案中,因为我发现如果数组内容像这样,效果不好:
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©
尽管很丑陋,但是只要您不显式传递数组,而是传递与数组相对应的变量,这是一种可行的解决方法:
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[@]"}
然后在内部使用相比,这是一个更好的解决方案array_inside=("$@")
。当还有其他位置/ getopts
参数时,这变得很复杂。在这些情况下,我必须先确定和然后使用shift
和删除数组元素的组合来删除与数组无关的参数。
一个纯粹主义者的观点可能认为这种方法违反了语言,但是从务实的角度来说,这种方法为我节省了很多麻烦。在一个相关的主题上,我还使用eval
了一个内部构造的数组给根据target_varname
我传递给函数的参数命名的变量:
eval $target_varname=$"(${array_inside[@]})"
希望这对某人有帮助。
要求:在数组中查找字符串的函数。
这是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
我的简短答案是:
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[*]}
都用“”括起来,否则会失败。