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

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


当前回答

注:这是我在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'

现代bash(显然是4.3或更高版本)允许通过引用传递数组。我将在下面展示。如果您希望手动序列化和反序列化数组,请参阅我在这里对bash常规“索引”数组和bash关联数组的回答。但是,如下所示,通过引用传递数组要简单得多,也更简洁,所以我现在推荐这样做。

下面的代码也可以在我的eRCaGuy_hello_world repo中在线获得:array_pass_as_bash_parameter_by_reference.sh。另请参阅这里的示例:array_pass_as_bash_parameter_2_associate .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"

样例输出:

摩登原始人 巴尼废墟

...下面是一个关联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"

样例输出:

摩登原始人 巴尼废墟

引用:

我从@Todd Lehman的回答中修改了上面的代码示例:如何在Bash中将关联数组作为参数传递给函数? 请参见我的手动序列化/反序列化答案 并在这里查看我的后续问题:为什么man bash页面声明declare和local -n属性“不能应用于数组变量”,但它可以?

这里的基本问题是,设计/实现数组的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[*]}"

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

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

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

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

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

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

通过这种方式,你实际上可以传递多个数组作为参数,它不必是最后一个参数。

这条即使有空格也适用:

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