【问题标题】:Passing arrays as parameters in bash在bash中将数组作为参数传递
【发布时间】:2010-11-06 23:51:18
【问题描述】:

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

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

【问题讨论】:

标签: arrays bash


【解决方案1】:

现代 bash (apparently version 4.3 or later),允许您通过引用传递数组。我将在下面展示。如果您想手动序列化和反序列化数组,请改用see my answer here for bash regular "indexed" arrayshere for bash associative arrays。如下所示,通过引用传递数组更简单、更简洁,不过,这就是我现在推荐的方式。

下面的代码也可以在我的eRCaGuy_hello_world repo 中在线获得: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"

样本输出:

Fred Flintstone
Barney Rubble

...这里是 关联 bash 数组的演示(即: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["a"]}"
    echo "${data_ref["b"]}"
}

# declare a bash associative array
declare -A data
data["a"]="Fred Flintstone"
data["b"]="Barney Rubble"
foo "data"

样本输出:

Fred Flintstone
Barney Rubble

参考资料:

  1. 我从@Todd Lehman 的回答中修改了上述代码示例:How to pass an associative array as argument to a function in Bash?
  2. 另见my manual serializing/deserializing answer here
  3. 并在此处查看我的后续问题:Why do the man bash pages state the declare and local -n attribute "cannot be applied to array variables", and yet it can?

【讨论】:

    【解决方案2】:

    下面的答案向您展示了如何将 bash 常规“索引”数组作为参数传递给函数,主要是通过序列化和反序列化

    1. 要查看本手册序列化/反序列化 bash 关联数组(哈希表)而不是常规索引数组,see my answer here
    2. 要获得更好的方法(我认为需要 bash 版本 4.3 或更高版本)通过引用传递数组,请参阅上面的链接和my other answer here .
      1. 通过引用传递数组更容易、更简洁,所以我现在建议这样做。话虽如此,我在下面展示的手动序列化/反序列化技术也非常有用。

    快速总结:
    请参阅下面的 3 个单独的函数定义。我复习一下如何通过:

    1. 一个 bash 数组到一个函数
    2. 一个函数的两个或多个 bash 数组,以及
    3. 两个或多个 bash 数组加上函数的附加参数(在数组之前或之后)。

    12 年后,我仍然没有在这里看到任何我真正喜欢的答案,我认为这些答案足够彻底、足够简单和“规范”,足以让我使用——我可以回来的答案一次又一次地复制和粘贴并在需要时展开。所以,这是我的答案,我认为是所有这些事情。

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

    您也可以将其称为“bash 函数或脚本中的可变参数解析”,特别是因为传递给下面示例的每个数组中的元素数量可以动态变化,并且在 bash 中,数组的元素本质上是传递给即使通过像 "${array1[@]}" 这样的单个数组扩展参数传入数组,该函数也可以作为单独的输入参数。

    对于下面的所有示例代码,假设您有这两个 bash 数组进行测试:

    array1=()
    array1+=("one")
    array1+=("two")
    array1+=("three")
    
    array2=("four" "five" "six" "seven" "eight")
    

    上面和下面的代码可在我的bash/array_pass_as_bash_parameter.sh 文件中找到,该文件位于我在 GitHub 上的eRCaGuy_hello_world 存储库中。

    示例 1:如何将一个 bash 数组传递给函数

    要将数组传递给 bash 函数,您必须分别传递其所有元素。 给定 bash 数组array1,获取该数组所有元素的语法为"${array1[@]}"。由于 bash 函数或可执行文件的所有传入参数都包含在名为 @ 的魔法 bash 输入参数数组中,因此您可以使用 "$@" 语法读取输入数组的所有成员,如下所示。

    函数定义:

    # Print all elements of a bash array.
    # General form:
    #       print_one_array array1
    # Example usage:
    #       print_one_array "${array1[@]}"
    print_one_array() {
        for element in "$@"; do
            printf "    %s\n" "$element"
        done
    }
    

    示例用法:

    echo "Printing array1"
    # This syntax passes all members of array1 as separate input arguments to 
    # the function
    print_one_array "${array1[@]}"
    

    示例输出:

    Printing array1
        one
        two
        three
    

    示例 2:如何将两个或多个 bash 数组传递给函数...

    (以及如何再次将输入数组重新捕获为单独的 bash 数组)

    这里,我们需要区分哪些传入的参数属于哪个数组。为此,我们需要知道每个数组的大小,即每个数组中元素的个数。这与在 C 中传递数组非常相似,我们通常还必须知道传递给任何 C 函数的数组长度。给定 bash 数组array1,其中的元素个数可以用"${#array1[@]}" 获得(注意# 符号的用法)。为了知道array_len长度参数在输入参数中的位置,我们必须始终为每个数组传递数组长度参数传递单个数组之前元素,如下图。

    为了解析数组,我在输入参数数组@上使用数组切片

    这里提醒一下 bash 数组切片语法的工作原理(来自 my answer here)。在切片语法:start:length 中,第一个数字是开始切片的从零开始的索引,第二个数字是要抓取的元素数:

    # array slicing basic format 1: grab a certain length starting at a certain
    # index
    echo "${@:2:5}"
    #         │ │
    #         │ └────> slice length
    #         └──────> slice starting index (zero-based)
    
    # array slicing basic format 2: grab all remaining array elements starting at a
    # certain index through to the end
    echo "${@:2}"
    #         │
    #         │
    #         └──────> slice starting index (zero-based)
    

    另外,为了强制将输入数组中的切片参数变成一个新数组,我将它们括在括号中(),就像这样,例如("${@:$i:$array1_len}")。再一次,外面的括号很重要,因为这就是我们在 bash 中创建数组的方式。

    下面的这个例子只接受两个 bash 数组,但是按照给定的模式,它可以很容易地接受 任意数量的 bash 数组作为参数。

    函数定义:

    # Print all elements of two bash arrays.
    # General form (notice length MUST come before the array in order
    # to be able to parse the args!):
    #       print_two_arrays array1_len array1 array2_len array2
    # Example usage:
    #       print_two_arrays "${#array1[@]}" "${array1[@]}" \
    #       "${#array2[@]}" "${array2[@]}"
    print_two_arrays() {
        # For debugging: print all input args
        echo "All args to 'print_two_arrays':"
        print_one_array "$@"
    
        i=1
    
        # Read array1_len into a variable
        array1_len="${@:$i:1}"
        ((i++))
        # Read array1 into a new array
        array1=("${@:$i:$array1_len}")
        ((i += $array1_len))
    
        # Read array2_len into a variable
        array2_len="${@:$i:1}"
        ((i++))
        # Read array2 into a new array
        array2=("${@:$i:$array2_len}")
        ((i += $array2_len))
    
        # Print the two arrays
        echo "array1:"
        print_one_array "${array1[@]}"
        echo "array2:"
        print_one_array "${array2[@]}"
    }
    

    示例用法:

    echo "Printing array1 and array2"
    print_two_arrays "${#array1[@]}" "${array1[@]}" "${#array2[@]}" "${array2[@]}"
    

    示例输出:

    Printing array1 and array2
    All args to 'print_two_arrays':
        3
        one
        two
        three
        5
        four
        five
        six
        seven
        eight
    array1:
        one
        two
        three
    array2:
        four
        five
        six
        seven
        eight
    

    示例 3:将 两个 bash 数组加上之后的一些额外参数传递给一个函数

    这是对上述示例的微小扩展。它还使用 bash 数组切片,就像上面的示例一样。然而,我们并没有在解析两个完整的输入数组后停止,而是在最后继续并解析更多的参数。对于任意数量的 bash 数组和任意数量的附加参数,只要每个 bash 数组的长度恰好位于该数组的元素之前,这种模式就可以无限期地继续下去,以适应任何输入参数顺序。

    函数定义:

    # Print all elements of two bash arrays, plus two extra args at the end.
    # General form (notice length MUST come before the array in order
    # to be able to parse the args!):
    #       print_two_arrays_plus_extra_args array1_len array1 array2_len array2 \
    #       extra_arg1 extra_arg2
    # Example usage:
    #       print_two_arrays_plus_extra_args "${#array1[@]}" "${array1[@]}" \
    #       "${#array2[@]}" "${array2[@]}" "hello" "world"
    print_two_arrays_plus_extra_args() {
        i=1
    
        # Read array1_len into a variable
        array1_len="${@:$i:1}"
        ((i++))
        # Read array1 into a new array
        array1=("${@:$i:$array1_len}")
        ((i += $array1_len))
    
        # Read array2_len into a variable
        array2_len="${@:$i:1}"
        ((i++))
        # Read array2 into a new array
        array2=("${@:$i:$array2_len}")
        ((i += $array2_len))
    
        # You can now read the extra arguments all at once and gather them into a
        # new array like this:
        extra_args_array=("${@:$i}")
    
        # OR you can read the extra arguments individually into their own variables
        # one-by-one like this
        extra_arg1="${@:$i:1}"
        ((i++))
        extra_arg2="${@:$i:1}"
        ((i++))
    
        # Print the output
        echo "array1:"
        print_one_array "${array1[@]}"
        echo "array2:"
        print_one_array "${array2[@]}"
        echo "extra_arg1 = $extra_arg1"
        echo "extra_arg2 = $extra_arg2"
        echo "extra_args_array:"
        print_one_array "${extra_args_array[@]}"
    }
    

    示例用法:

    echo "Printing array1 and array2 plus some extra args"
    print_two_arrays_plus_extra_args "${#array1[@]}" "${array1[@]}" \
    "${#array2[@]}" "${array2[@]}" "hello" "world"
    

    示例输出:

    Printing array1 and array2 plus some extra args
    array1:
        one
        two
        three
    array2:
        four
        five
        six
        seven
        eight
    extra_arg1 = hello
    extra_arg2 = world
    extra_args_array:
        hello
        world
    

    参考资料:

    1. 我在eRCaGuy_hello_world repo 中引用了很多我自己的示例代码:
      1. array_practice.sh
      2. array_slicing_demo.sh
    2. [我对 bash 数组切片的回答]Unix & Linux: Bash: slice of positional parameters
    3. An answer to my question on "How can I create and use a backup copy of all input args ("$@") in bash?" - 非常对于输入参数数组的一般数组操作很有用
    4. An answer to "How to pass array as an argument to a function in Bash",这向我证实了这个非常重要的概念:

      你不能传递一个数组,你只能传递它的元素(即扩展的数组)。

    另见:

    1. [我关于这个话题的另一个答案]How to pass array as an argument to a function in Bash

    【讨论】:

      【解决方案3】:

      我的简短回答是:

      function display_two_array {
          local arr1=$1
          local arr2=$2
          for i in $arr1
          do
             echo "arrary1: $i"
          done
          
          for i in $arr2
          do
             echo "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[*]}要用""括起来,否则会失败。

      【讨论】:

      • 您的示例不正确,因为它不完整。请给出完整的脚本代码。
      • “片段”仅适用于 HTML 和 JavaScript。使用{} 按钮以代码突出显示其他语言的代码。
      【解决方案4】:

      注意:这是我在 Stack Overflow 上找不到答案后自己发布的有点粗糙的解决方案。它只允许传递一个数组,并且它是参数列表的最后一个元素。实际上,它根本不是传递数组,而是传递其元素的列表,这些元素通过调用函数()重新组装成一个数组,但它对我有用。稍后,Ken 发布了他的解决方案,但我将我的解决方案保留在这里以供“历史”参考。

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

      【讨论】:

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

      您可以使用以下方式传递多个数组作为参数

      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
      

      编辑/注释:(来自下面的 cmets)

      • descTableoptsTable 作为名称传递并在函数中展开。因此,当作为参数给出时,不需要$
      • 请注意,即使 descTable 等被定义为 local,这仍然有效,因为局部变量对于它们调用的函数是可见的。
      • ${!1} 中的 ! 扩展了 arg 1 变量。
      • declare -a 只是使索引数组显式,并非绝对必要。

      【讨论】:

      • 需要注意的是,如果原始数组是稀疏的,那么接收函数中的数组将不会有相同的索引。
      • 这太棒了,但是 Ken 或其他人能否解释一些令我困惑的事情: 1 - 我原以为 descTable 和 optsTable 在传递时必须以 $ 为前缀作为函数参数。 2 - 在“takes...”的第一行,为什么需要明确的数组声明? 3 - 那是什么!在表达式 ${!1} 中表示,为什么 [@] 不是必需的,甚至是不允许的? -- 这行得通,根据我的测试,似乎需要所有这些细节,但我想了解原因!
      • 1:descTable 和 optsTable 只是作为名称传递,因此没有 $,它们应该只在被调用函数中扩展 2:不完全确定,但我认为这不是真的必要 3: !之所以使用,是因为传递给函数的参数需要扩展两次:$1 扩展为“descTable[@]”,应该扩展为“${descTable[@]}”。 ${!1} 语法就是这样做的。
      • 我认为“declare -a”部分是不必要的。括号的存在已经将赋值的 LHS 定义为一个数组。
      • 这个回答帮我解决了刚才的一个问题。但是,我想指出,在我的机器上(使用 bash 4.3.42)“${!1}”和“${!2}”需要去掉引号。如果不这样做,则将原始数组的值读取为一个字符串并分别分配给argAry1[0]和argAry2[0],基本上意味着数组结构丢失了。
      【解决方案6】:

      虽然丑陋,但只要您不显式传递数组,而是传递与数组对应的变量,这里有一个解决方法:

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

      希望这对某人有所帮助。

      【讨论】:

        【解决方案7】:

        只是添加到接受的答案,因为我发现如果数组内容类似于:

        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©

        【讨论】:

          【解决方案8】:

          这里的基本问题是设计/实现数组的 ba​​sh 开发人员真的把狗搞砸了。他们认为${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[*]}"
          

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

          【讨论】:

          • 使用local -n 的解决方案比公认的答案更好,更新。此解决方案也适用于任何类型的变量。此答案中列出的示例可以缩短为local -n ARR=${1}。但是,local/declare-n 选项仅在 Bash 4.3 及更高版本中可用。
          • 这很好!小问题:如果你传递一个与函数的本地参数同名的变量(例如funky ARR),shell 会给出警告circular name reference,因为基本上函数会尝试执行local -n ARR=ARR。好 discussion 关于这个话题。
          • 阅读该页面后,我得出的结论是:1. 在 bash 中使用数组是一个非常好的想法 2. 如果您的想法是写一些优雅的东西,请使用 IFS 并避免在 bash 中使用数组。跨度>
          【解决方案9】:

          要求:在数组中查找字符串的函数。
          这是对 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 
          

          【讨论】:

            【解决方案10】:

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

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

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

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

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

            【讨论】:

              【解决方案11】:

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

              工作原理

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

              1. 这实际上是创建descTableoptsTable 数组的副本,takes_ary_as_arg 函数可以访问这些数组。
              2. takes_ary_as_arg() 函数接收descTable[@]optsTable[@] 作为字符串,这意味着$1 == descTable[@]$2 == optsTable[@]
              3. takes_ary_as_arg()函数的开头它使用${!parameter}语法,称为indirect reference or sometimes double referenced,这意味着不是使用$1的值,而是使用的值$1的扩展,例如:

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

                $2 也是如此。

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

              注意: 值得一提的是,使用这种括号形式的数组初始化会根据IFSInternal Field Separator 来初始化新数组,默认情况下 tab换行空格。在这种情况下,由于它使用了[@] 表示法,因此每个元素都被视为自己被引用(与[*] 相反)。

              我对它的预订

              BASH 中,局部变量作用域是当前函数和从它调用的每个子函数,这意味着takes_ary_as_arg() 函数“看到”那些descTable[@]optsTable[@] 数组,因此它正在工作(见上面的解释)。

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

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

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

              总结

              从本质上讲,这并没有按价值传递——像往常一样。

              我还想强调丹尼斯威廉姆森上面的评论:sparse 数组(没有定义所有键的数组 - 其中有“孔”)将无法按预期工作 - 我们会松开键和“压缩”数组。

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

              • 对于~“副本”:这种技术已经足够好了,只需要注意索引(键)已经消失了。
              • 对于真实副本: 我们可以对键使用 eval,例如:

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

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

              • 当然,如果我们要传递descTableoptsTable 字符串(没有[@]),我们可以将数组本身(如引用)与eval 一起使用。对于接受数组的通用函数。

              【讨论】:

              • 对 Ken Bertelson 解释背后的机制进行了很​​好的解释。对于“既然如此,为什么不直接查看这些变量本身?”的问题,我将回答:只是为了重用函数。假设我需要用Array1 调用一个函数,然后用Array2 调用,传递数组名称就很方便了。
              【解决方案12】:
              function aecho {
                set "$1[$2]"
                echo "${!1}"
              }
              

              例子

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

              【讨论】:

                【解决方案13】:

                通过一些技巧,您实际上可以将命名参数与数组一起传递给函数。

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

                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'
                

                【讨论】:

                  【解决方案14】:

                  这个甚至可以使用空格:

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

                  【讨论】:

                  • 我想知道这里有什么意义。这只是正常的参数传递。 "$@" 语法适用于空格:"$@" 等价于 "$1" "$2"...
                  • 我可以将 2 个数组传递给一个函数吗?
                  【解决方案15】:

                  DevSolar 的回答有一点我不明白(也许他有特定的原因,但我想不出一个):他从位置参数中逐个元素地设置数组,迭代。

                  一个更简单的方法是

                  called_function()
                  {
                    ...
                    # do everything like shown by DevSolar
                    ...
                  
                    # now get a copy of the positional parameters
                    local_array=("$@")
                    ...
                  }
                  

                  【讨论】:

                  • 我不这样做的原因是直到几天前我还没有玩过 bash 数组。以前,如果 Perl 变得复杂,我会切换到它,这是我目前工作中没有的选择。感谢您的提示!
                  猜你喜欢
                  • 1970-01-01
                  • 2018-02-15
                  • 2011-08-06
                  • 2013-05-03
                  • 2014-10-30
                  • 2013-07-07
                  • 2017-09-26
                  • 1970-01-01
                  相关资源
                  最近更新 更多