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

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


当前回答

评论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中使用数组本身(如通过引用)。用于接受数组的泛型函数。

其他回答

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

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

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

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 aecho {
  set "$1[$2]"
  echo "${!1}"
}

例子

$ foo=(dog cat bird)

$ aecho foo 1
cat

这里的基本问题是,设计/实现数组的bash开发人员真的把事情搞砸了。他们认为${array}只是${array[0]}的简称,这是一个严重的错误。特别是当您认为${array[0]}没有意义并且如果数组类型是关联的,则计算结果为空字符串时。

Assigning an array takes the form array=(value1 ... valueN) where value has the syntax [subscript]=string, thereby assigning a value directly to a particular index in the array. This makes it so there can be two types of arrays, numerically indexed and hash indexed (called associative arrays in bash parlance). It also makes it so that you can create sparse numerically indexed arrays. Leaving off the [subscript]= part is short hand for a numerically indexed array, starting with the ordinal index of 0 and incrementing with each new value in the assignment statement.

因此,${array}应该计算为整个数组,索引和所有。它的求值应该是赋值语句的倒数。任何计算机科学专业的三年级学生都应该知道这一点。在这种情况下,这段代码将完全按照你所期望的那样工作:

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

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