52

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

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

任何想法将不胜感激。

4

9 回答 9

43

现在是 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来声明对名为 by 的变量的nameref$1,这意味着它不是对$1自身的引用,而是对名称 $1持有的变量的引用,即SOME_VAR在我们的例子中。右侧的值应该只是一个命名现有变量的字符串:您如何获取字符串并不重要,所以类似local -n ref="my_var"or的东西也local -n ref=$(get_var_name)可以工作。declare也可以local在允许/要求的上下文中替换。有关详细信息,请参阅Bash 参考手册中有关外壳参数的章节。

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

于 2018-05-10T21:21:27.367 回答
25

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

    如果参数的第一个字符是感叹号 (!),则
    引入了变量间接级别。Bash 使用的值
    由参数的其余部分形成的变量作为
    多变的; 然后扩展此变量并将该值用于
    其余的替换,而不是参数的值
    本身。这称为间接扩展。

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

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

$ a=1 b=2
$ swap a b
$ echo $a $b
2 1
于 2013-02-13T11:25:44.847 回答
17

使用辅助函数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不能分配数组。

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

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

即使local b是,冲突仍然存在unset

g() { local b; unset b; eval $1=bar; }  # WRONG
g b                                     # Still conflicts with `local b'
echo $b                                 # b is empty unexpected
于 2010-05-17T20:19:37.280 回答
14

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

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

于 2009-02-12T07:24:29.830 回答
12

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

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

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

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

于 2009-02-12T07:57:03.903 回答
6

好的,所以这个问题一直在等待一个“真正的”解决方案,我很高兴地说,我们现在可以完全不使用 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}"
    
}

运行时输出:

referenceTester:
        the value of my reference is:
                I am a variable
passedByRef:
        the value of my reference is:
                I am a variable
passedByRef:
        value of my reference set to:
                some other value
referenceTester:
        the value of my reference is now:
                some other value,
        and the pointed to variable:
                some other value
于 2016-09-22T16:33:47.250 回答
2

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

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

$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
于 2009-02-12T21:32:24.583 回答
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 变量的内容。一开始,变量是空的,每次调用函数时都会添加内容。分隔符可以根据需要选择。“eval”起始行更新宠物变量。

于 2014-10-10T19:25:48.433 回答
1

这就是在 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
于 2017-07-05T15:55:33.513 回答