代码之家  ›  专栏  ›  技术社区  ›  DevSolar

在bash中传递数组作为参数

  •  174
  • DevSolar  · 技术社区  · 15 年前

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

    注: 在这里没有找到关于堆栈溢出的答案之后,我自己发布了一些粗糙的解决方案。它只允许传递一个数组,并且它是参数列表的最后一个元素。实际上,它根本不是传递数组,而是一个元素列表,它的元素被称为_Function()重新组合到数组中,但它对我有效。如果有人知道更好的方法,可以在这里添加。

    12 回复  |  直到 6 年前
        1
  •  202
  •   Eliran Malka    8 年前

    你可以通过 多个数组作为参数 使用类似的方法:

    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
    
        2
  •  82
  •   DevSolar    9 年前

    注: 这是我自己发布的有点粗糙的解决方案,在这里没有找到关于堆栈溢出的答案。它只允许传递一个数组,并且它是参数列表的最后一个元素。实际上,它根本不是传递数组,而是一个元素列表,它的元素被称为_Function()重新组合到数组中,但它对我有效。后来肯公布了他的解决方案,但我把我的保留在这里作为“历史性”参考。

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

    由彭赛改进,谢谢。

        3
  •  35
  •   Édouard Lopez    8 年前

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

    它是如何工作的

    这个 takes_ary_as_arg descTable[@] optsTable[@] 行在 try_with_local_arys() 函数发送:

    1. 这实际上是创建 descTable optsTable 可以访问的数组 takes_ary_as_arg 功能。
    2. takes_ary_as_arg() 函数接收 descTable[@] optsTable[@] 作为字符串,这意味着 $1 == descTable[@] $2 == optsTable[@] .
    3. 在…的开始 以\元\为\元()。 它使用的功能 ${!parameter} 语法,称为 indirect reference or sometimes double referenced ,这意味着 而不是使用 $1 的值,我们使用 扩大 价值 1美元 例如:

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

      同样地 $2 .

    4. 把这个放进去 argAry1=("${!1}") 创造 argAry1 作为一个数组(括号如下 = )随着 下降[ @ ] 就像在那里写作一样 argAry1=("${descTable[@]}") 直接。 这个 declare 不需要。

    N.B.: 值得一提的是,使用此括号形式的数组初始化将根据 IFS 内部字段分隔符 默认为 标签 , 换行符 空间 . 在这种情况下,因为它使用 [@] 符号:每个元素本身就被看作是被引用的(与 [*] )

    我用它预订的

    BASH ,局部变量范围是当前函数及其调用的每个子函数,这转化为 以\元\为\元()。 功能“看到”那些 下降[ @ ] 不稳定的[ @ ] 数组,因此它可以工作(参见上面的解释)。

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

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

    见上述解释,仅复制 下降[ @ ] 数组的值根据当前值 IFS .

    综上所述

    从本质上讲,这是没有价值的传递——和往常一样。

    我还想强调丹尼斯·威廉姆森在上面的评论: 稀疏的 数组(没有所有键定义的数组-其中有“孔”)将无法按预期工作-我们将释放键并“压缩”数组。

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

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

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

    然后循环使用它们创建一个副本。 注:这里 ! 它不是以前的间接/双重计算,而是在数组上下文中返回数组索引(键)。

    • 当然,如果我们通过 可下降的 不稳定的 字符串(无 [@ ] ,我们可以将数组本身(如中的引用)用于 eval . 对于接受数组的泛型函数。
        4
  •  19
  •   Benjamin W. Loc Tran    7 年前

    这里的基本问题是设计/实现数组的bash开发人员真的搞砸了pooch。他们决定 ${array} 只是为了 ${array[0]} 这是一个严重的错误。尤其是当你认为 $ {数组〔0〕} 如果数组类型是关联的,则没有意义并计算为空字符串。

    分配数组的形式为 array=(value1 ... valueN) 其中value具有语法 [subscript]=string 从而将值直接分配给数组中的特定索引。这使得它可以有两种类型的数组,数字索引和哈希索引(用bash术语称为关联数组)。它还可以创建稀疏的数字索引数组。离开 [subscript]= 部分是数字索引数组的缩写,从0的序数索引开始,并在赋值语句中以每个新值递增。

    因此, $ {数组} 应该对 整个的 数组、索引和所有。它应该计算为赋值语句的倒数。任何三年级的学生都应该知道这一点。在这种情况下,此代码将完全按照您的预期工作:

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

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

        5
  •  5
  •   TheBonsai    15 年前

    Devsolar的答案有一点我不理解(也许他有一个具体的理由这样做,但我想不出一个):他从位置参数一个元素一个元素地设置阵列,迭代。

    一个更容易接近的方法是

    called_function()
    {
      ...
      # do everything like shown by DevSolar
      ...
    
      # now get a copy of the positional parameters
      local_array=("$@")
      ...
    }
    
        6
  •  3
  •   Zombo    9 年前
    function aecho {
      set "$1[$2]"
      echo "${!1}"
    }
    

    例子

    $ foo=(dog cat bird)
    
    $ aecho foo 1
    cat
    
        7
  •  2
  •   Remy Cilia    7 年前

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

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

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

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

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

        8
  •  1
  •   humbleSapiens    13 年前

    这一个甚至适用于空间:

    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[@]}"
    
        9
  •  1
  •   niieani    9 年前

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

    我开发的方法允许您访问传递给如下函数的参数:

    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'
    
        10
  •  1
  •   AlvaroGMJ    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盎司

        11
  •  1
  •   Blake Schultze    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[@]})"

    希望这能帮助别人。

        12
  •  0
  •   Andre    7 年前

    要求 :函数在数组中查找字符串。
    这稍微简化了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