我想询问是否可以通过引用将参数传递给脚本函数:
即在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
任何想法将不胜感激.
从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
这是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
声明一个nameref到名为的变量$1
,意味着它不是对$1
自身的引用,而是一个名称所在 的变量$1
,即SOME_VAR
在我们的例子中.在右边的值应该只是一个字符串,指定一个现有的变量:不要紧,你如何得到字符串,所以像local -n ref="my_var"
或local -n ref=$(get_var_name)
将工作太.declare
也可以local
在允许/要求的上下文中替换.有关详细信息,请参阅Bash参考手册中有关Shell参数的章节.
这种方法的优点是(可以说)更好的可读性,最重要的是,避免eval
,其安全陷阱很多并且记录良好.
使用辅助函数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
和printf
如果变量恰巧宣布将不工作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
我找到了一种方法来做到这一点,但我不确定这是多么正确:
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 ...
Bash没有内置的引用,所以基本上你能够做你想做的唯一方法是将函数传递给你想要修改的全局变量的名称.即便如此,你还需要一份eval
声明:
boo() { eval ${1}="new" } SOME_VAR="old" echo $SOME_VAR # old boo "SOME_VAR" echo $SOME_VAR # new
我不认为你可以在这里使用间接引用,因为Bash自动访问其名称存储在间接引用中的变量的值.它没有给你机会设置它.
好的,所以这个问题已经有一段时间等待“真正的”解决方案了,我很高兴地说,我们现在可以完全不用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