通过引用传递参数

时间:2009-02-12 06:45:31

标签: bash shell scripting pass-by-reference

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

即。在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
}

那么在BASH我想做的事情如下:

function boo () 
{

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

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

    var2='new'    # only changes the local copy 
    #$1='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

任何想法都会受到赞赏。

9 个答案:

答案 0 :(得分:23)

从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.

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

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

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

答案 1 :(得分:16)

使用辅助函数upvar

# Assign variable one scope above the caller.
# Usage: local "$1" && upvar $1 value [value ...]
# Param: $1  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 "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
         fi
    fi
}

Newfun()

中使用它
local "$1" && upvar $1 new

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

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

问题:

eval $1=new

如果$1碰巧包含命令,则不安全:

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

最好使用printf -v

printf -v "$1" %s new

但是printf -v无法分配数组。

此外,如果变量恰好声明为eval,则printflocal都无效:

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

即使local bunset

,冲突仍然存在
g() { local b; unset b; eval $1=bar; }  # WRONG
g b                                     # Still conflicts with `local b'
echo $b                                 # b is empty unexpected

答案 2 :(得分:15)

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

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

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

这里的关键部分是:

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

  • 在函数内部,使用 local -n ref=$1 $1命名的变量声明 nameref ,这意味着它不是参考到$1本身,而不是名称 $1包含的变量,在我们的例子中是SOME_VAR。右侧的值应该只是一个命名现有变量的字符串:与获取字符串的方式无关,因此local -n ref="my_var"local -n ref=$(get_var_name)之类的内容也可以。 declare也可以在允许/要求的上下文中替换local。有关详细信息,请参阅chapter on Shell Parameters in Bash Reference Manual

这种方法的优点是(可以说)更好的可读性,最重要的是,避免eval,其安全隐患很多并且记录良好。

答案 3 :(得分:12)

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

Newfun()
{
    local var1="$1"
    eval $var1=2
    # or can do eval $1=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

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

答案 4 :(得分:11)

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

boo() {
    eval ${1}="new"
}

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

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

答案 5 :(得分:4)

好的,所以这个问题一直在等待'真正的'解决方案已经有一段时间了,我很高兴地说我们现在可以在不使用eval的情况下实现这一目标。

要记住的是在调用者中声明一个引用作为被调用者,至少在我的示例中是这样的:

#!/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=$1

        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

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:2)

#!/bin/bash

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

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

输出:

cat
cat|dog
cat|dog|hamster

调用该函数的结构是:

append_string  name_of_var_to_update  string_to_add  name_of_var_containing_sep_char

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

答案 8 :(得分:0)

这对我在Ubuntu bash shell上的作用

#!/bin/sh

iteration=10

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


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