在bash中将数组作为参数传递

时间:2009-06-30 12:21:40

标签: arrays bash

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

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

13 个答案:

答案 0 :(得分:205)

您可以使用以下内容传递多个数组作为参数

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

答案 1 :(得分:83)

注意:这是我自己发布的有点粗糙的解决方案,之后在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=("${@}")
}

由TheBonsai改进,谢谢。

答案 2 :(得分:36)

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

如何运作

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

  1. 这实际上是创建descTableoptsTable数组的副本,takes_ary_as_arg函数可以访问这些数组。
  2. takes_ary_as_arg()函数接收descTable[@]optsTable[@]字符串,表示$1 == descTable[@]$2 == optsTable[@]
  3. takes_ary_as_arg()函数的开头,它使用${!parameter}语法,称为indirect reference or sometimes double referenced,这意味着而不是使用$1的值,我们使用$1 扩展值的值,例如:

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

    同样适用于$2

  4. 将其添加到argAry1=("${!1}")中,使用展开的argAry1创建=数组(descTable[@]后面的括号),就像直接写argAry1=("${descTable[@]}")一样。 declare没有必要。
  5. NB:值得一提的是,使用此括号表单的数组初始化根据IFS内部字段分隔符初始化新数组标签换行符空间。在这种情况下,因为它使用了[@]符号,所以每个元素都被看作是被引用的(与[*]相反)。

    我的预订

    BASH中,局部变量范围是当前函数以及从中调用的每个子函数,这转换为takes_ary_as_arg()函数“看到”那些descTable[@]和{{1数组,因此它正在工作(参见上面的解释)。

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

    optsTable[@]

    参见上面的解释,它只根据当前argAry1=("${descTable[@]}") 复制descTable[@]数组的值。

    总结

      

    这实际上是没有任何价值 - 像往常一样。

    我还想强调丹尼斯威廉姆森上面的评论:稀疏数组(没有所有键定义的数组 - 其中带有“漏洞”)将无法按预期工作 - 我们会松开键和“浓缩“阵列。

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

    • for~“copy”:这个技术已经足够好了,只需要注意,索引(键)就不见了。
    • 用于真实副本: 我们可以使用eval作为键,例如:

      IFS

    然后使用它们创建一个副本。 注意:此处eval local keys=(\${!$1}) 未使用它以前的间接/双重评估,而是在数组上下文中返回数组索引(键)。

    • 当然,如果我们要传递!descTable字符串(没有optsTable),我们可以使用数组本身(如引用中所示){{1 }}。对于接受数组的通用函数。

答案 3 :(得分:19)

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

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

因此,${array}应该评估整个数组,索引和所有。它应该评估赋值语句的反转。任何第三年CS专业都应该知道。在这种情况下,此代码将完全按照您的预期工作:

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

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

答案 4 :(得分:5)

DevSolar的答案有一点我不明白(也许他有一个特定的理由这样做,但我想不到一个):他从元素迭代的位置参数元素设置数组。

更容易approuch

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

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

答案 5 :(得分:3)

function aecho {
  set "$1[$2]"
  echo "${!1}"
}

实施例

$ foo=(dog cat bird)

$ aecho foo 1
cat

答案 6 :(得分:2)

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

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

然后,您可以在代码中提取它,如下所示:

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

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

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

答案 7 :(得分:1)

这个甚至可以用空格:

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

答案 8 :(得分:1)

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

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

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'

答案 9 :(得分:1)

只是添加到接受的答案中,因为我发现如果数组内容有点像这样的话,它就不能正常工作:

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©

答案 10 :(得分:1)

丑陋的是,这是一种变通方法,只要您没有显式传递数组,而是传递与数组相对应的变量,便可以使用:

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[@]"}传递,然后使用{{1}内部访问它是一个更好的解决方案。 }。当还有其他position / array_inside=("$@")参数时,这变得很复杂。在这些情况下,我必须先确定然后使用getopts和数组元素删除的某种组合来删除与数组不相关的参数。

一个纯粹主义者的观点很可能将此方法视为对语言的侵犯,但从务实的角度来说,这种方法使我免于很多痛苦。在相关主题上,我还使用shift将内部构造的数组分配给根据传递给函数的参数eval命名的变量:

target_varname

希望这对某人有帮助。

答案 11 :(得分:0)

要求:在数组中查找字符串的函数 这是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 

答案 12 :(得分:0)

我的简短答案是:

function display_two_array {
    local arr1=$1
    local arr2=$2
    for i in $arr1
    do
       "arrary1: $i"
    done
    
    for i in $arr2
    do
       "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[*]}应该用“”包围,否则会失败。