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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1063347/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 18:18:15  来源:igfitidea点击:

Passing arrays as parameters in bash

arraysbash

提问by DevSolar

How can I pass an array as parameter to a bash function?

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

Note:After not finding an answer here on Stack Overflow, I posted my somewhat crude solution myself. It allows for only one array being passed, and it being the last element of the parameter list. Actually, it is not passing the array at all, but a list of its elements, which are re-assembled into an array by called_function(), but it worked for me. If someone knows a better way, feel free to add it here.

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

回答by Ken Bertelson

You can pass multiple arrays as argumentsusing something like this:

您可以使用以下方法将多个数组作为参数传递:

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

will echo:

会回声:

sli4-iread sli4-iwrite sli3-iread sli3-iwrite  
--msix  --iread --msix  --iwrite --msi   --iread --msi   --iwrite

Edit/notes:(from comments below)

编辑/注释:(来自下面的评论)

  • descTableand optsTableare passed as names and are expanded in the function. Thus no $is needed when given as parameters.
  • Note that this still works even with descTableetc being defined with local, because locals are visible to the functions they call.
  • The !in ${!1}expands the arg 1 variable.
  • declare -ajust makes the indexed array explicit, it is not strictly necessary.
  • descTableoptsTable作为名称传递并在函数中扩展。因此$当作为参数给出时不需要。
  • 请注意,即使使用descTable定义了etc ,这仍然有效local,因为局部变量对它们调用的函数是可见的。
  • !${!1}扩展ARG 1可变。
  • declare -a只是使索引数组显式,并不是绝对必要的。

回答by DevSolar

Note:This is the somewhat crude solution I posted myself, after not finding an answer here on Stack Overflow. It allows for only one array being passed, and it being the last element of the parameter list. Actually, it is not passing the array at all, but a list of its elements, which are re-assembled into an array by called_function(), but it worked for me. Somewhat later Ken posted his solution, but I kept mine here for "historic" reference.

注意:这是我在 Stack Overflow 上找不到答案后自己发布的有点粗糙的解决方案。它只允许传递一个数组,并且它是参数列表的最后一个元素。实际上,它根本没有传递数组,而是传递其元素的列表,这些元素通过 call_function() 重新组装成数组,但它对我有用。稍后 Ken 发布了他的解决方案,但我将我的解决方案保留在这里以供“历史性”参考。

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

called_function()
{
    local_variable=""
    shift
    local_array=("${@}")
}

Improved by TheBonsai, thanks.

由 TheBonsai 改进,谢谢。

回答by TheWizard

Commenting on Ken Bertelson solution and answering Jan Hettich:

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

How it works

这个怎么运作

the takes_ary_as_arg descTable[@] optsTable[@]line in try_with_local_arys()function sends:

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

  1. This is actually creates a copy of the descTableand optsTablearrays which are accessible to the takes_ary_as_argfunction.
  2. takes_ary_as_arg()function receives descTable[@]and optsTable[@]as strings, that means $1 == descTable[@]and $2 == optsTable[@].
  3. in the beginning of takes_ary_as_arg()function it uses ${!parameter}syntax, which is called indirect referenceor sometimes double referenced, this means that instead of using $1's value, we use the value of the expandedvalue of $1, example:

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

    likewise for $2.

  4. putting this in argAry1=("${!1}")creates argAry1as an array (the brackets following =) with the expanded descTable[@], just like writing there argAry1=("${descTable[@]}")directly. the declarethere is not required.
  1. 这实际上是创建函数可访问的descTableoptsTable数组的副本takes_ary_as_arg
  2. takes_ary_as_arg()函数接收descTable[@]optsTable[@]作为字符串,这意味着$1 == descTable[@]$2 == optsTable[@]
  3. takes_ary_as_arg()函数的开头它使用${!parameter}语法,称为间接引用或有时是双重引用,这意味着我们不使用$1的值,而是使用 的扩展值的值$1,例如:

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

    同样对于$2.

  4. 把它作为一个带有扩展的数组(后面的括号)来argAry1=("${!1}")创建,就像直接在那里写一样。在那里不是必需的。argAry1=descTable[@]argAry1=("${descTable[@]}")declare

N.B.:It is worth mentioning that array initialization using this bracket form initializes the new array according to the IFSor Internal Field Separatorwhich is by default tab, newlineand space. in that case, since it used [@]notation each element is seen by itself as if he was quoted (contrary to [*]).

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

My reservation with it

我的预订

In BASH, local variable scope is the current function and every child function called from it, this translates to the fact that takes_ary_as_arg()function "sees" those descTable[@]and optsTable[@]arrays, thus it is working (see above explanation).

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

Being that case, why not directly look at those variables themselves? It is just like writing there:

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

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

See above explanation, which just copies descTable[@]array's values according to the current IFS.

见上面的解释,它只是descTable[@]根据当前的IFS.

In summary

总之

This is passing, in essence, nothing by value - as usual.

本质上,这没有任何价值传递 - 像往常一样。

I also want to emphasize Dennis Williamson comment above: sparsearrays (arrays without all the keys defines - with "holes" in them) will not work as expected - we would loose the keys and "condense" the array.

我还想强调 Dennis Williamson 上面的评论:稀疏数组(没有定义所有键的数组——其中有“洞”)不会按预期工作——我们会松开键并“压缩”数组。

That being said, I do see the value for generalization, functions thus can get the arrays (or copies) without knowing the names:

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

  • for ~"copies": this technique is good enough, just need to keep aware, that the indices (keys) are gone.
  • for real copies: we can use an eval for the keys, for example:

    eval local keys=(${!})
    
  • 对于〜“副本”:这种技术已经足够好了,只需要注意索引(键)已经消失了。
  • 对于真实副本:我们可以对键使用 eval,例如:

    eval local keys=(${!})
    

and then a loop using them to create a copy. Note: here !is not used it's previous indirect/double evaluation, but rather in array context it returns the array indices (keys).

然后循环使用它们来创建副本。注意:这里!没有使用它之前的间接/双重评估,而是在数组上下文中它返回数组索引(键)。

  • and, of course, if we were to pass descTableand optsTablestrings (without [@]), we could use the array itself (as in by reference) with eval. for a generic function that accepts arrays.
  • 而且,当然,如果我们要传递descTableoptsTable字符串(没有[@]),我们可以使用数组本身(如通过引用)和eval. 对于接受数组的通用函数。

回答by tigerand

The basic problem here is that the bash developer(s) that designed/implemented arrays really screwed the pooch. They decided that ${array}was just short hand for ${array[0]}, which was a bad mistake. Especially when you consider that ${array[0]}has no meaning and evaluates to the empty string if the array type is associative.

这里的基本问题是设计/实现数组的 ba​​sh 开发人员真的把狗搞砸了。他们认为这${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=(value1 ... valueN)其中 value 具有语法的形式[subscript]=string,从而将值直接分配给数组中的特定索引。这使得可以有两种类型的数组,数字索引和哈希索引(在 bash 术语中称为关联数组)。它还使您可以创建稀疏的数字索引数组。省略[subscript]=部分是数字索引数组的简写,从序数索引 0 开始,并随着赋值语句中的每个新值递增。

Therefore, ${array}should evaluate to the entirearray, indexes and all. It should evaluate to the inverse of the assignment statement. Any third year CS major should know that. In that case, this code would work exactly as you might expect it to:

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

declare -A foo bar
foo=${bar}

Then, passing arrays by value to functions and assigning one array to another would work as the rest of the shell syntax dictates. But because they didn't do this right, the assignment operator =doesn't work for arrays, and arrays can't be passed by value to functions or to subshells or output in general (echo ${array}) without code to chew through it all.

然后,按值将数组传递给函数并将一个数组分配给另一个数组将按照 shell 语法的其余部分进行工作。但是因为他们没有正确地做到这一点,赋值运算符=不适用于数组,并且数组不能按值传递给函数或子shell或一般的输出 ( echo ${array}) 没有代码来咀嚼它。

So, if it had been done right, then the following example would show how the usefulness of arrays in bash could be substantially better:

所以,如果它做得对,那么下面的例子将展示 bash 中数组的实用性如何大大提高:

simple=(first=one second=2 third=3)
echo ${simple}

the resulting output should be:

结果输出应该是:

(first=one second=2 third=3)

Then, arrays could use the assignment operator, and be passed by value to functions and even other shell scripts. Easily stored by outputting to a file, and easily loaded from a file into a script.

然后,数组可以使用赋值运算符,并按值传递给函数甚至其他 shell 脚本。通过输出到文件轻松存储,并轻松从文件加载到脚本中。

declare -A foo
read foo <file

Alas, we have been let down by an otherwise superlative bash development team.

唉,我们被一个其他方面最优秀的 bash 开发团队失望了。

As such, to pass an array to a function, there is really only one option, and that is to use the nameref feature:

因此,要将数组传递给函数,实际上只有一种选择,那就是使用 nameref 功能:

function funky() {
    local -n ARR

    ARR=
    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

will result in the following output:

将导致以下输出:

indexes: foo zoom
values: bar fast

Since this is passing by reference, you can also assign to the array in the function. Yes, the array being referenced has to have a global scope, but that shouldn't be too big a deal, considering that this is shell scripting. To pass an associative or sparse indexed array by value to a function requires throwing all the indexes and the values onto the argument list (not too useful if it's a large array) as single strings like this:

由于这是通过引用传递,您还可以在函数中分配给数组。是的,被引用的数组必须具有全局范围,但考虑到这是 shell 脚本,这应该没什么大不了的。要将关联或稀疏索引数组按值传递给函数,需要将所有索引和值作为单个字符串抛出到参数列表(如果它是大数组,则不太有用),如下所示:

funky "${!array[*]}" "${array[*]}"

and then writing a bunch of code inside the function to reassemble the array.

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

回答by TheBonsai

DevSolar's answer has one point I don't understand (maybe he has a specific reason to do so, but I can't think of one): He sets the array from the positional parameters element by element, iterative.

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

An easier approuch would be

一个更简单的方法是

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

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

回答by Steven Penny

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

Example

例子

$ foo=(dog cat bird)

$ aecho foo 1
cat

回答by Remy Cilia

An easy way to pass several arrays as parameter is to use a character-separated string. You can call your script like this:

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

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

Then, you can extract it in your code like this:

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

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

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

This way, you can actually pass multiple arrays as parameters and it doesn't have to be the last parameters.

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

回答by AlvaroGMJ

Just to add to the accepted answer, as I found it doesn't work well if the array contents are someting like:

只是添加到已接受的答案中,因为我发现如果数组内容类似于:

RUN_COMMANDS=(
  "command1 param1... paramN"
  "command2 param1... paramN"
)

In this case, each member of the array gets split, so the array the function sees is equivalent to:

在这种情况下,数组的每个成员都被拆分,因此函数看到的数组等效于:

RUN_COMMANDS=(
    "command1"
    "param1"
     ...
    "command2"
    ...
)

To get this case to work, the way I found is to pass the variable name to the function, then use eval:

为了让这个案例起作用,我发现的方法是将变量名传递给函数,然后使用 eval:

function () {
    eval 'COMMANDS=( "${'""'[@]}" )'
    for COMMAND in "${COMMANDS[@]}"; do
        echo $COMMAND
    done
}

function RUN_COMMANDS

Just my 2?

只有我的2?

回答by Blake Schultze

As ugly as it is, here is a workaround that works as long as you aren't passing an array explicitly, but a variable corresponding to an array:

尽管很丑陋,但这里有一个解决方法,只要您不显式传递数组,而是传递与数组对应的变量:

function passarray()
{
    eval array_internally=("$(echo '${''[@]}')")
    # 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

I'm sure someone can come up with a clearner implementation of the idea, but I've found this to be a better solution than passing an array as "{array[@]"}and then accessing it internally using array_inside=("$@"). This becomes complicated when there are other positional/getoptsparameters. In these cases, I've had to first determine and then remove the parameters not associated with the array using some combination of shiftand array element removal.

我相信有人可以对这个想法提出更清晰的实现,但我发现这比将数组传递为 as"{array[@]"}然后在内部使用array_inside=("$@"). 当存在其他位置/getopts参数时,这会变得复杂。在这些情况下,我必须首先确定,然后使用shift和 数组元素删除的某种组合来删除与数组无关的参数。

A purist perspective likely views this approach as a violation of the language, but pragmatically speaking, this approach has saved me a whole lot of grief. On a related topic, I also use evalto assign an internally constructed array to a variable named according to a parameter target_varnameI pass to the function:

纯粹主义的观点可能认为这种方法违反了语言,但从务实的角度来看,这种方法为我省去了很多悲伤。在相关主题上,我还使用eval将内部构造的数组分配给根据target_varname传递给函数的参数命名的变量:

eval $target_varname=$"(${array_inside[@]})"

eval $target_varname=$"(${array_inside[@]})"

Hope this helps someone.

希望这可以帮助某人。

回答by niieani

With a few tricks you can actually pass named parameters to functions, along with arrays.

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

The method I developed allows you to access parameters passed to a function like this:

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

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" = "" && echo correct
    #
    test "${anArrayWithFourElements[0]}" = "" && echo correct
    test "${anArrayWithFourElements[1]}" = "" && echo correct
    test "${anArrayWithFourElements[2]}" = "" && echo correct
    # etc...
    #
    test "${anotherArrayWithTwo[0]}" = "" && echo correct
    test "${anotherArrayWithTwo[1]}" = "" && echo correct
    #
    test "$anotherSingle" = "" && 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"

In other words, not only you can call your parameters by their names (which makes up for a more readable core), you can actually pass arrays (and references to variables - this feature works only in bash 4.3 though)! Plus, the mapped variables are all in the local scope, just as $1 (and others).

换句话说,您不仅可以通过名称调用参数(这构成了更具可读性的核心),您实际上还可以传递数组(以及对变量的引用——尽管此功能仅适用于 bash 4.3)!另外,映射的变量都在本地范围内,就像 $1(和其他)一样。

The code that makes this work is pretty light and works both in bash 3 and bash 4 (these are the only versions I've tested it with). If you're interested in more tricks like this that make developing with bash much nicer and easier, you can take a look at my Bash Infinity Framework, the code below was developed for that purpose.

使这项工作的代码非常简单,并且可以在 bash 3 和 bash 4 中运行(这些是我测试过的唯一版本)。如果您对更多这样的技巧感兴趣,这些技巧使使用 bash 开发变得更好更容易,您可以查看我的Bash Infinity Framework,下面的代码就是为此目的开发的。

Function.AssignParamLocally() {
    local commandWithArgs=(  )
    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'