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

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


当前回答

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

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

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©

注:这是我在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=("${@}")
}

尽管它很丑,但这里有一个变通方法,只要你没有显式地传递一个数组,而是一个对应于数组的变量:

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

希望这能帮助到一些人。

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

To see this manual serializing/deserializing for bash associative arrays (hash tables) instead of for regular indexed arrays, see my answer here. For a better way (which requires bash version 4.3 or later, I think), which passes the arrays by reference, see the link just above and my other answer here. Passing arrays by reference is much easier and more-concise, so that's what I now recommend. That being said, the manual serializing/deserializing techniques I show below are also extremely informative and useful.

快速总结:

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

一个bash数组对应一个函数 两个或多个bash数组到一个函数,和 一个函数的两个或多个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文件在我的eRCaGuy_hello_world repo上GitHub。

例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数组)

Here, we need to differentiate which incoming parameters belong to which array. To do this, we need to know the size of each array, meaning the number of elements in each array. This is very similar to passing arrays in C, where we also generally must know the array length passed to any C function. Given bash array array1, the number of elements in it can be obtained with "${#array1[@]}" (notice the usage of the # symbol). In order to know where in the input arguments the array_len length parameter is, we must always pass the array length parameter for each array before passing the individual array elements, as shown below.

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

这里有一个关于bash数组切片语法如何工作的提示(来自我的回答)。在切片语法: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

引用:

I referenced a lot of my own sample code from my eRCaGuy_hello_world repo here: array_practice.sh array_slicing_demo.sh [my answer on bash array slicing] Unix & Linux: Bash: slice of positional parameters An answer to my question on "How can I create and use a backup copy of all input args ("$@") in bash?" - very useful for general array manipulation of the input argument array An answer to "How to pass array as an argument to a function in Bash", which confirmed to me this really important concept that: You cannot pass an array, you can only pass its elements (i.e. the expanded array).

参见:

[我关于这个主题的另一个答案]如何在Bash中将数组作为参数传递给函数

DevSolar的回答有一点我不理解(也许他有特定的理由这么做,但我想不出一个):他根据位置参数一个元素一个元素地迭代设置数组。

一个更简单的解释是

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

  # now get a copy of the positional parameters
  local_array=("$@")
  ...
}