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

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


当前回答

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

一个更简单的解释是

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

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

其他回答

你也可以用数组创建一个json文件,然后用jq解析这个json文件

例如:

my-array.json:

{
  "array": ["item1","item2"]
}

script.sh:

ARRAY=$(jq -r '."array"' $1 | tr -d '[],"')

然后像这样调用脚本:

script.sh ./path-to-json/my-array.json

您可以在这个类似的问题中找到更多的想法:如何将数组作为参数传递给Bash中的函数

我的简短回答是:

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[*]}应该被""包围,否则就会失败。

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

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

希望这能帮助到一些人。

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

它是如何工作的

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

This is actually creates a copy of the descTable and optsTable arrays which are accessible to the takes_ary_as_arg function. takes_ary_as_arg() function receives descTable[@] and optsTable[@] as strings, that means $1 == descTable[@] and $2 == optsTable[@]. in the beginning of takes_ary_as_arg() function it uses ${!parameter} syntax, which is called indirect reference or sometimes double referenced, this means that instead of using $1's value, we use the value of the expanded value of $1, example: baba=booba variable=baba echo ${variable} # baba echo ${!variable} # booba likewise for $2. putting this in argAry1=("${!1}") creates argAry1 as an array (the brackets following =) with the expanded descTable[@], just like writing there argAry1=("${descTable[@]}") directly. the declare there is not required.

注意:值得一提的是,使用这个括号形式的数组初始化根据IFS或内部字段分隔符初始化新数组,默认为制表符、换行符和空格。在这种情况下,因为它使用[@]符号,所以每个元素都被看作是被引用的(与[*]相反)。

我对它的预订

在BASH中,局部变量作用域是当前函数和从它调用的每个子函数,这转化为这样一个事实,takes_ary_as_arg()函数“看到”那些descTable[@]和optsTable[@]数组,因此它是工作的(见上述解释)。

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

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

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

总之

从本质上讲,这只是过去,没有任何价值——和往常一样。

我还想强调Dennis Williamson上面的评论:稀疏数组(没有所有键定义的数组-其中有“孔”)不会像预期的那样工作-我们会失去键并“压缩”数组。

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

对于~“拷贝”:这个技术已经足够好了,只是需要注意,索引(键)已经没有了。 对于真实副本: 我们可以对键使用eval,例如: Eval本地键=(\${!$1})

然后循环使用它们来创建副本。 注:这里!不是使用它之前的间接/双重求值,而是在数组上下文中返回数组下标(键)。

当然,如果我们要传递descTable和optsTable字符串(不带[@]),我们可以在eval中使用数组本身(如通过引用)。用于接受数组的泛型函数。

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

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

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'