在bash中将数组作为参数传递

Dev*_*lar 181 arrays bash

如何将数组作为参数传递给bash函数?

注意:在Stack Overflow上没有找到答案后,我自己发布了一些粗略的解决方案.它只允许传递一个数组,它是参数列表的最后一个元素.实际上,它根本没有传递数组,而是传递了它的元素列表,这些元素通过called_function()重新组合成一个数组,但它对我有用.如果有人知道更好的方式,请随时在此处添加.

小智 213

您可以使用以下内容将多个数组作为参数传递:

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
Run Code Online (Sandbox Code Playgroud)

将回应:

sli4-iread sli4-iwrite sli3-iread sli3-iwrite  
--msix  --iread --msix  --iwrite --msi   --iread --msi   --iwrite
Run Code Online (Sandbox Code Playgroud)

  • 需要注意的一点是,如果原始数组是稀疏的,则接收函数中的数组将不具有相同的索引. (14认同)
  • 这很棒,但Ken或者其他人可以解释一些令我困惑的事情:1 - 我会认为descTable和optsTable在作为函数参数传递时必须以$为前缀.2 - 在"take ..."的第一行,为什么需要显式数组声明?3 - 那是什么!表达式为$ {!1},为什么[@]不需要甚至不允许? - 这是有效的,所有这些细节似乎都需要根据我的测试,但我想了解原因! (13认同)
  • 1:descTable和optsTable只是作为名称传递,因此没有$,它们只能在被调用的函数2中展开:不完全确定,但我认为这不是真的必要3:!因为传递给函数的参数需要扩展两次:$ 1扩展为"descTable [@]",并且应该扩展为"$ {descTable [@]}".$ {!1}语法就是这样做的. (8认同)
  • 我不认为"声明-a"部分是必要的.括号的存在已经将赋值的LHS定义为数组. (8认同)
  • 这个答案帮我解决了一个问题.但是,我想指出在我的机器上(使用bash 4.3.42),"$ {!1}"和"$ {!2}"需要删除引号.如果不这样做,原始数组的值将被读取为一个字符串并分别分配给argAry1 [0]和argAry2 [0],这基本上意味着数组结构丢失. (3认同)
  • 有意思......这意味着,`try_with_local_arys`中的局部变量在它调用的函数中是可见的,例如`takes_ary_as_arg`. (2认同)

Dev*_*lar 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=("${@}")
}
Run Code Online (Sandbox Code Playgroud)

由TheBonsai改进,谢谢.

  • 事实发生三年之后,这个答案 - 仅仅因为历史原因而保留 - 在几天之内就收到了两次.正如通常所说的那样,没有任何关于为什么人们认为这是有道理的说明.请注意,这个答案*早于所有其他答案,并且我接受肯的答案作为最佳解决方案.我完全清楚它远远不够完美,但是四个月*它是SO上最好的.为什么在它取得第二名之后,它应该被投入两年*,这是肯的完美解决方案超出了我. (18认同)
  • 好吧,如果包含每个数组的元素数,则可以传递多个数组.`called_function"$ {#array [@]}""$ {array [@]}""$ {#array2 [@]}""$ {array2 [@]}"`等...仍有一些明显的限制,但实际上,更好地以语言支持的方式解决问题,而不是试图将语言弯曲到您习惯于其他语言的方式. (4认同)
  • 我知道你问了这个问题,你写了这个答案,并且你接受了错误的答案.这就是为什么我这样说的.接受的答案是坏的原因是因为它试图通过引用传递数组,这是你应该真正避免的.此外,该示例将多个参数混合为单个字符串.如果你真的需要通过引用传递数组,那么bash是错误的语言.即使使用bash 4.3的新nameref变量,也无法安全地避免名称冲突(循环引用). (2认同)
  • @geirha:好吧,我想我们必须同意我们不同意,你必须让我来判断哪个答案最能回答我的问题。就个人而言,我更喜欢通过引用传递数组*反正*(无论语言如何,以保存数据复制);当替代方案是向后弯腰并将数组大小作为附加参数传递时更是如此...... (2认同)

小智 37

评论Ken Bertelson解决方案并回答Jan Hettich:

这个怎么运作

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

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

    baba=booba
    variable=baba
    echo ${variable} # baba
    echo ${!variable} # booba
    
    Run Code Online (Sandbox Code Playgroud)

    同样的$2.

  4. 把它放在一个数组(后面的括号)argAry1=("${!1}")创建扩展,就像直接写在那里.在那里不是必需的.argAry1=descTable[@]argAry1=("${descTable[@]}")declare

NB:值得一提的使用该托架形式的数组初始化初始化根据新阵列IFS内部字段分隔符,其是通过默认标签,换行空间.在这种情况下,因为它使用了[@]符号,所以每个元素本身都被看作是被引用(与之相反[*]).

我的预订

BASH,局部变量范围是当前函数和从它调用的每个子函数,这转换为takes_ary_as_arg()函数"看到"那些descTable[@]optsTable[@]数组的事实,因此它正在工作(参见上面的解释).

既然如此,为什么不直接看看那些变量呢?这就像写在那里:

argAry1=("${descTable[@]}")
Run Code Online (Sandbox Code Playgroud)

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

综上所述

从本质上讲,这通过没有任何价值 - 像往常一样.

我还要强调丹尼斯威廉姆森上面的评论:稀疏数组(没有所有键定义的数组 - 其中带有"孔")将无法按预期工作 - 我们将松开键并"压缩"数组.

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

  • 对于〜"副本":这种技术足够好,只需要意识到索引(键)已经消失.
  • 对于真实副本:我们可以使用eval作为密钥,例如:

    eval local keys=(\${!$1})
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 当然,如果我们要传递descTableoptsTable字符串(没有[@]),我们可以使用数组本身(如引用中所示)eval.对于接受数组的泛型函数.

  • Ken Bertelson解释背后机制的良好解释.对于"那样的情况,为什么不直接查看那些变量本身?",我将回答:只是为了重用该函数.假设我需要使用`Array1`调用函数,然后调用`Array2`,传递数组名称变得很方便. (2认同)

小智 20

这里的基本问题是设计/实现数组的bash开发人员确实搞砸了这个小狗.他们认为这${array}只是空手而归${array[0]},这是一个错误的错误.特别是当您认为${array[0]}没有意义时,如果数组类型是关联的,则计算空字符串.

分配数组采用array=(value1 ... valueN)value具有语法的形式[subscript]=string,从而将值直接分配给数组中的特定索引.这使得它可以有两种类型的数组,数字索引和哈希索引(在bash用语中称为关联数组).它还使您可以创建稀疏的数字索引数组.离开[subscript]=零件是数字索引数组的简写,从序数索引0开始,并在赋值语句中使用每个新值递增.

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

declare -A foo bar
foo=${bar}
Run Code Online (Sandbox Code Playgroud)

然后,按值将数组传递给函数并将一个数组分配给另一个数组将起到shell语法的其余部分的作用.但是因为它们没有正确地执行此操作,赋值运算符=不适用于数组,并且数组不能通过值传递给函数或子shell或输出general(echo ${array})而不需要代码来咀嚼它们.

所以,如果它已经正确完成,那么下面的例子将说明bash中数组的有用性如何可以更好:

simple=(first=one second=2 third=3)
echo ${simple}
Run Code Online (Sandbox Code Playgroud)

结果输出应该是:

(first=one second=2 third=3)
Run Code Online (Sandbox Code Playgroud)

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

declare -A foo
read foo <file
Run Code Online (Sandbox Code Playgroud)

唉,我们已经被一个优秀的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
Run Code Online (Sandbox Code Playgroud)

将导致以下输出:

indexes: foo zoom
values: bar fast
Run Code Online (Sandbox Code Playgroud)

由于这是通过引用传递的,因此您也可以在函数中分配数组.是的,被引用的数组必须具有全局范围,但考虑到这是shell脚本,这不应该是一个大问题.要按值将关联或稀疏索引数组传递给函数,需要将所有索引和值抛出到参数列表中(如果它是一个大型数组,则不太有用)作为单个字符串,如下所示:

funky "${!array[*]}" "${array[*]}"
Run Code Online (Sandbox Code Playgroud)

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

  • 使用“local -n”的解决方案比公认的答案更好、更及时。此解决方案也适用于任何类型的变量。此答案中列出的示例可以缩短为“local -n ARR=${1}”。然而,`local`/`declare` 的 `-n` 选项仅在 Bash 4.3 及更高版本中可用。 (2认同)

Gab*_*les 6

如何在 bash 中通过引用将常规数组和关联数组作为参数传递

现代 bash(显然是 4.3 或更高版本)允许您通过引用传递数组。我将在下面展示这一点。如果您想手动序列化和反序列化数组,请参阅我的答案,这里是 bash 常规“索引”数组这里是 bash 关联数组。要按值或引用打印数组,请参阅我的答案此处

然而,通过引用传递数组(如下所示)更加容易和简洁,所以这就是我现在推荐的。

下面的代码也可以在我的eRCaGuy_hello_world存储库中在线获取:array_pass_as_bash_parameter_by_reference.sh。另请参阅此处的示例:array_pass_as_bash_parameter_2_associative.sh

这是常规 bash 数组的演示:

function foo {
    # declare a local **reference variable** (hence `-n`) named `data_ref`
    # which is a reference to the value stored in the first parameter
    # passed in
    local -n data_ref="$1"
    echo "${data_ref[0]}"
    echo "${data_ref[1]}"
}

# declare a regular bash "indexed" array
declare -a data
data+=("Fred Flintstone")
data+=("Barney Rubble")
foo "data"
Run Code Online (Sandbox Code Playgroud)

示例输出:

Fred Flintstone
Barney Rubble
Run Code Online (Sandbox Code Playgroud)

...这是关联 bash 数组的演示(即:bash 哈希表、“字典”或“无序映射”):

Fred Flintstone
Barney Rubble
Run Code Online (Sandbox Code Playgroud)

示例输出:

Fred Flintstone
Barney Rubble
Run Code Online (Sandbox Code Playgroud)

参考:

  1. 我修改了 @Todd Lehman 的答案中的上述代码示例:How to pass an associative array as argument to a function in Bash?
  2. 另请参阅我的手动序列化/反序列化答案
  3. 请参阅我的后续问题:为什么man bash页面声明declarelocal -n属性“不能应用于数组变量”,但它可以?


The*_*sai 5

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

一个更容易的approuch将是

called_function()
{
  ...
  # do everything like shown by DevSolar
  ...

  # now get a copy of the positional parameters
  local_array=("$@")
  ...
}
Run Code Online (Sandbox Code Playgroud)