bash 通过引用传递参数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/540298/
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 17:56:40  来源:igfitidea点击:

Passing arguments by reference

bashshellscriptingpass-by-reference

提问by RomanM

I want to ask if it is possible to pass arguments to a script function by reference:

我想问一下是否可以通过引用将参数传递给脚本函数:

i.e. to do something that would look like this in C++:

即在 C++ 中做一些看起来像这样的事情:

void boo(int &myint) { myint = 5; }

int main() {
    int t = 4;
    printf("%d\n", t); // t->4
    boo(t);
    printf("%d\n", t); // t->5
}

So then in BASH I want to do something like:

那么在 BASH 中我想做一些类似的事情:

function boo () 
{

    var1=       # now var1 is global to the script but using it outside
                  # this function makes me lose encapsulation

    local var2= # so i should use a local variable ... but how to pass it back?

    var2='new'    # only changes the local copy 
    #='new'     this is wrong of course ...
    # ${!1}='new' # can i somehow use indirect reference?
}           

# call boo
SOME_VAR='old'
echo $SOME_VAR # -> old
boo "$SOME_VAR"
echo $SOME_VAR # -> new

Any thoughts would be appreciated.

任何想法将不胜感激。

采纳答案by Sergey Shevchenko

It's 2018, and this question deserves an update. At least in Bash, as of Bash 4.3-alpha, you can use namerefsto pass function arguments by reference:

现在是 2018 年,这个问题值得更新。至少在 Bash 中,从 Bash 4.3-alpha 开始,您可以使用namerefs通过引用传递函数参数:

function boo() 
{
    local -n ref=
    ref='new' 
}

SOME_VAR='old'
echo $SOME_VAR # -> old
boo SOME_VAR
echo $SOME_VAR # -> new

The critical pieces here are:

这里的关键部分是:

  • Passing the variable's name to boo, not its value: boo SOME_VAR, not boo $SOME_VAR.

  • Inside the function, using local -n ref=$1to declare a namerefto the variable named by $1, meaning it's not a reference to $1itself, but rather to a variable whose name$1holds, i.e. SOME_VARin our case. The value on the right-hand side should just be a string naming an existing variable: it doesn't matter how you get the string, so things like local -n ref="my_var"or local -n ref=$(get_var_name)would work too. declarecan also replace localin contexts that allow/require that. See chapter on Shell Parameters in Bash Reference Manualfor more information.

  • 将变量的名称传递给 boo,而不是它的值:boo SOME_VAR,而不是boo $SOME_VAR

  • 在函数内部, usinglocal -n ref=$1为名为 by 的变量声明一个nameref$1,这意味着它不是对$1自身的引用,而是对其名称$1保存的变量引用,即SOME_VAR在我们的例子中。右侧的值应该只是一个命名现有变量的字符串:如何获取字符串并不重要,所以像local -n ref="my_var"or 之类的东西local -n ref=$(get_var_name)也可以。declare也可以local在允许/要求的情况下替换。有关更多信息,请参阅Bash 参考手册中有关 Shell 参数的章节

The advantage of this approach is (arguably) better readability and, most importantly, avoiding eval, whose security pitfalls are many and well-documented.

这种方法的优点是(可以说)更好的可读性,最重要的是避免了eval,其安全陷阱很多且有据可查。

回答by Andreas Spindler

From the Bash man-page (Parameter Expansion):

从 Bash 手册页(参数扩展):

    If the first  character of parameter is an exclamation  point (!), a
    level of variable indirection is  introduced. Bash uses the value of
    the variable  formed from the rest  of parameter as the  name of the
    variable; this variable  is then expanded and that value  is used in
    the rest  of the  substitution, rather than  the value  of parameter
    itself. This is known as indirect expansion.

Therefore a reference is the variable's name. Here is a swapfunction using variable indirection that does not require a temporary variable:

因此,引用是变量的名称。这是一个swap使用变量间接的函数,不需要临时变量:

function swap()
{   # 
    # @param VARNAME1 VARNAME2
    #
    eval "=${!2} =${!1}"
}

$ a=1 b=2
$ swap a b
$ echo $a $b
2 1

回答by Freddy Vulto

Use a helper function upvar:

使用辅助函数upvar

# Assign variable one scope above the caller.
# Usage: local "" && upvar  value [value ...]
# Param:   Variable name to assign value to
# Param: $*  Value(s) to assign.  If multiple values, an array is
#            assigned, otherwise a single value is assigned.
# NOTE: For assigning multiple variables, use 'upvars'.  Do NOT
#       use multiple 'upvar' calls, since one 'upvar' call might
#       reassign a variable to be used by another 'upvar' call.
# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
upvar() {
    if unset -v ""; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval =\"$2\"          # Return single value
        else
            eval =\(\"${@:2}\"\)  # Return array
         fi
    fi
}

And use it like this from within Newfun():

并从内部像这样使用它Newfun()

local "" && upvar  new

For returning multiple variables, use another helper function upvars. This allows passing multiple variables within one call, thus avoiding possible conflicts if one upvarcall changes a variable used in another subsequent upvarcall.

要返回多个变量,请使用另一个辅助函数upvars。这允许在一次调用中传递多个变量,从而避免在一次upvar调用更改另一次后续upvar调用中使用的变量时可能发生的冲突。

See: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_referencefor helper function upvarsand more information.

请参阅:http: //www.fvue.nl/wiki/Bash : _Passing_variables_by_reference了解辅助函数upvars和更多信息。

The problem with:

问题在于:

eval =new

is that it's not safe if $1happens to contain a command:

是如果$1碰巧包含命令是不安全的:

set -- 'ls /;true'
eval =new  # Oops

It would be better to use printf -v:

最好使用printf -v

printf -v "" %s new

But printf -vcannot assign arrays.

printf -v不能分配数组。

Moreover, both evaland printfwon't work if the variable happens to be declared local:

此外,如果变量恰好被声明,则evalprintf都将不起作用local

g() { local b; eval =bar; }  # WRONG
g b                            # Conflicts with `local b'
echo $b                        # b is empty unexpected

The conflict stays there even if local bis unset:

冲突仍然存在,即使local bunset

g() { local b; unset b; eval =bar; }  # WRONG
g b                                     # Still conflicts with `local b'
echo $b                                 # b is empty unexpected

回答by RomanM

I have found a way to do this but I am not sure how correct this is:

我找到了一种方法来做到这一点,但我不确定这是多么正确:

Newfun()
{
    local var1=""
    eval $var1=2
    # or can do eval =2 if no local var
}

var=1
echo  var is $var    # $var = 1
newfun 'var'         # pass the name of the variable…
echo now var is $var # $var = 2

So we pass the variable name as opposed to the value and then use eval ...

所以我们传递变量名而不是值,然后使用 eval ...

回答by David Z

Bash doesn't have anything like references built into it, so basically the only way you would be able to do what you want is to pass the function the name of the global variable you want it to modify. And even then you'll need an evalstatement:

Bash 没有内置任何类似引用的东西,所以基本上你能够做你想做的唯一方法是将你想要它修改的全局变量的名称传递给函数。即便如此,你也需要一个eval声明:

boo() {
    eval ="new"
}

SOME_VAR="old"
echo $SOME_VAR # old
boo "SOME_VAR"
echo $SOME_VAR # new

I don't think you can use indirect references here because Bash automatically accesses the value of the variable whose name is stored in the indirect reference. It doesn't give you the chance to set it.

我不认为您可以在这里使用间接引用,因为 Bash 会自动访问名称存储在间接引用中的变量的值。它没有给你设置它的机会。

回答by Aethalides

Ok, so this question has been waiting for a 'real' solution for some time now, and I am glad to say that we can now accomplish this without using eval at all.

好的,所以这个问题已经等待了一段时间的“真正”解决方案,我很高兴地说我们现在可以在不使用 eval 的情况下完成此任务。

The keyto remember is to declare a reference in both the caller as the callee, at least in my example:

要记住的关键是在调用方和被调用方中声明一个引用,至少在我的例子中是这样:

#!/bin/bash

# NOTE this does require a bash version >= 4.3

set -o errexit -o nounset -o posix -o pipefail

passedByRef() {

    local -n theRef

    if [ 0 -lt $# ]; then

        theRef=

        echo -e "${FUNCNAME}:\n\tthe value of my reference is:\n\t\t${theRef}"

        # now that we have a reference, we can assign things to it

        theRef="some other value"

        echo -e "${FUNCNAME}:\n\tvalue of my reference set to:\n\t\t${theRef}"

    else

        echo "Error: missing argument"

        exit 1
    fi
}

referenceTester() {

    local theVariable="I am a variable"

    # note the absence of quoting and escaping etc.

    local -n theReference=theVariable

    echo -e "${FUNCNAME}:\n\tthe value of my reference is:\n\t\t${theReference}"

    passedByRef theReference

    echo -e "${FUNCNAME}:\n\tthe value of my reference is now:\n\t\t${theReference},\n\tand the pointed to variable:\n\t\t${theVariable}"

}

# run it

referenceTester

回答by ajaaskel

#!/bin/bash

append_string()
{
if [ -z "${!1}" ]; then
eval "=''"
else
eval "='${!1}''${!3}'''"
fi
}

PETS=''
SEP='|'
append_string "PETS" "cat" "SEP"
echo "$PETS"
append_string "PETS" "dog" "SEP"
echo "$PETS"
append_string "PETS" "hamster" "SEP"
echo "$PETS"

Output:

输出:

cat
cat|dog
cat|dog|hamster

Structure for calling that function is:

调用该函数的结构是:

append_string  name_of_var_to_update  string_to_add  name_of_var_containing_sep_char

Name of variable is passed to fuction about PETS and SEP while string to append is passed the usual way as value. "${!1}" refers to contents of global PETS variable. In the beginning that variable is empty and contens is added each time we call the function. Separator character can be selected as needed. "eval" starting lines update PETS variable.

变量的名称被传递给关于 PETS 和 SEP 的函数,而要附加的字符串作为值传递给通常的方式。“${!1}”是指全局PETS变量的内容。一开始,变量是空的,每次调用函数时都会添加内容。可以根据需要选择分隔符。“eval”起始行更新PETS变量。

回答by Ian Kelling

Eval should never be used on a string that a user can set because its dangerous. Something like "string; rm -rf ~" will be bad. So generally its best to find solutions where you don't have to worry about it.

不应在用户可以设置的字符串上使用 Eval,因为它很危险。像“string; rm -rf ~”这样的东西会很糟糕。因此,通常最好找到您不必担心的解决方案。

However, eval will be needed to set the passed variables, as the comment noted.

但是,正如注释所述,需要 eval 来设置传递的变量。

$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=; echo ${!x}; }
$ foo four
4

回答by enthusiasticgeek

This is what works for me on Ubuntu bash shell

这是在 Ubuntu bash shell 上对我有用的方法

#!/bin/sh

iteration=10

increment_count()
{
  local i
  i=$((+1))
  eval =$i
}


increment_count iteration
echo $iteration #prints 11
increment_count iteration
echo $iteration #prints 12