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
Passing arguments 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
, notboo $SOME_VAR
.Inside the function, using
local -n ref=$1
to declare a namerefto the variable named by$1
, meaning it's not a reference to$1
itself, but rather to a variable whose name$1
holds, i.e.SOME_VAR
in 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 likelocal -n ref="my_var"
orlocal -n ref=$(get_var_name)
would work too.declare
can also replacelocal
in contexts that allow/require that. See chapter on Shell Parameters in Bash Reference Manualfor more information.
将变量的名称传递给 boo,而不是它的值:
boo SOME_VAR
,而不是boo $SOME_VAR
。在函数内部, using
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 参考手册中有关 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 swap
function 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 upvar
call changes a variable used in another subsequent upvar
call.
要返回多个变量,请使用另一个辅助函数upvars
。这允许在一次调用中传递多个变量,从而避免在一次upvar
调用更改另一次后续upvar
调用中使用的变量时可能发生的冲突。
See: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_referencefor helper function upvars
and 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 $1
happens 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 -v
cannot assign arrays.
但printf -v
不能分配数组。
Moreover, both eval
and printf
won't work if the variable happens to be declared local
:
此外,如果变量恰好被声明,则eval
和printf
都将不起作用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 b
is unset
:
冲突仍然存在,即使local b
是unset
:
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 eval
statement:
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