Bash 中的动态变量名
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16553089/
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
Dynamic variable names in Bash
提问by Konstantinos
I am confused about a bash script.
我对 bash 脚本感到困惑。
I have the following code:
我有以下代码:
function grep_search() {
magic_way_to_define_magic_variable_=`ls | tail -1`
echo $magic_variable_
}
I want to be able to create a variable name containing the first argument of the command and bearing the value of e.g. the last line of ls
.
我希望能够创建一个变量名,其中包含命令的第一个参数并带有ls
.
So to illustrate what I want:
所以为了说明我想要什么:
$ ls | tail -1
stack-overflow.txt
$ grep_search() open_box
stack-overflow.txt
So, how should I define/declare $magic_way_to_define_magic_variable_$1
and how should I call it within the script?
那么,我应该如何定义/声明$magic_way_to_define_magic_variable_$1
以及如何在脚本中调用它?
I have tried eval
, ${...}
, \$${...}
, but I am still confused.
我试过eval
, ${...}
, \$${...}
,但我仍然很困惑。
采纳答案by chepner
Use an associative array, with command names as keys.
使用关联数组,以命令名称作为键。
# Requires bash 4, though
declare -A magic_variable=()
function grep_search() {
magic_variable[]=$( ls | tail -1 )
echo ${magic_variable[]}
}
If you can't use associative arrays (e.g., you must support bash
3), you can use declare
to create dynamic variable names:
如果不能使用关联数组(例如,必须支持bash
3),则可以使用declare
创建动态变量名:
declare "magic_variable_=$(ls | tail -1)"
and use indirect parameter expansion to access the value.
并使用间接参数扩展来访问该值。
var="magic_variable_"
echo "${!var}"
See BashFAQ: Indirection - Evaluating indirect/reference variables.
请参阅 BashFAQ:间接 - 评估间接/引用变量。
回答by Yorik.sar
I've been looking for better way of doing it recently. Associative array sounded like overkill for me. Look what I found:
我最近一直在寻找更好的方法。关联数组对我来说听起来有点矫枉过正。看我发现了什么:
suffix=bzz
declare prefix_$suffix=mystr
...and then...
...进而...
varname=prefix_$suffix
echo ${!varname}
回答by Ma?lan
Beyond associative arrays, there are several ways of achieving dynamic variables in Bash. Note that all these techniques present risks, which are discussed at the end of this answer.
除了关联数组,在 Bash 中还有几种实现动态变量的方法。请注意,所有这些技术都存在风险,这将在本答案的末尾进行讨论。
In the following examples I will assume that i=37
and that you want to alias the variable named var_37
whose initial value is lolilol
.
在以下示例中,我将假设i=37
您希望var_37
为其初始值为lolilol
.
Method 1. Using a “pointer” variable
方法 1. 使用“指针”变量
You can simply store the name of the variable in an indirection variable, not unlike a C pointer. Bash then has a syntax for readingthe aliased variable: ${!name}
expands to the value of the variable whose name is the value of the variable name
. You can think of it as a two-stage expansion: ${!name}
expands to $var_37
, which expands to lolilol
.
您可以简单地将变量的名称存储在一个间接变量中,这与 C 指针不同。然后,Bash 具有读取别名变量的语法:${!name}
扩展为名称为变量值的变量的值name
。你可以把它想象成一个两阶段的扩展:${!name}
扩展到$var_37
,扩展到lolilol
。
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
Unfortunately, there is no counterpart syntax for modifyingthe aliased variable. Instead, you can achieve assignment with one of the following tricks.
不幸的是,没有用于修改别名变量的对应语法。相反,您可以使用以下技巧之一来实现分配。
1a. Assigning with eval
1a. 分配与eval
eval
is evil, but is also the simplest and most portable way of achieving our goal. You have to carefully escape the right-hand side of the assignment, as it will be evaluated twice. An easy and systematic way of doing this is to evaluate the right-hand side beforehand (or to use printf %q
).
eval
是邪恶的,但也是实现我们目标的最简单、最便携的方式。您必须小心地避开赋值的右侧,因为它将被评估两次。一种简单而系统的方法是预先评估右侧(或使用printf %q
)。
Andyou should check manually that the left-hand side is a valid variable name, or a name with index (what if it was evil_code #
?). By contrast, all other methods below enforce it automatically.
并且您应该手动检查左侧是一个有效的变量名,还是一个带索引的名称(如果是evil_code #
?)。相比之下,下面的所有其他方法都会自动强制执行。
# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit
value='babibab'
eval "$name"='$value' # carefully escape the right-hand side!
echo "$var_37" # outputs “babibab”
Downsides:
缺点:
- does not check the validity of the variable name.
eval
is evil.eval
is evil.eval
is evil.
- 不检查变量名的有效性。
eval
是邪恶的。eval
是邪恶的。eval
是邪恶的。
1b. Assigning with read
1b. 分配与read
The read
builtin lets you assign values to a variable of which you give the name, a fact which can be exploited in conjunction with here-strings:
该read
内建让您指定值,其中你给的名称,可以在这里与弦一起利用的事实变量:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
The IFS
part and the option -r
make sure that the value is assigned as-is, while the option -d ''
allows to assign multi-line values. Because of this last option, the command returns with an non-zero exit code.
该IFS
部分和选项-r
确保该值被分配原样,而选项-d ''
允许指定多行值。由于最后一个选项,该命令返回一个非零退出代码。
Note that, since we are using a here-string, a newline character is appended to the value.
请注意,由于我们使用的是 here-string,因此该值会附加一个换行符。
Downsides:
缺点:
- somewhat obscure;
- returns with a non-zero exit code;
- appends a newline to the value.
- 有点晦涩;
- 返回非零退出代码;
- 将换行符附加到值。
1c. Assigning with printf
1c。分配与printf
Since Bash 3.1 (released 2005), the printf
builtin can also assign its result to a variable whose name is given. By contrast with the previous solutions, it just works, no extra effort is needed to escape things, to prevent splitting and so on.
从 Bash 3.1(2005 年发布)开始,printf
内置函数还可以将其结果分配给一个给定名称的变量。与之前的解决方案相比,它只是有效,不需要额外的努力来逃避事物,防止分裂等。
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
Downsides:
缺点:
- Less portable (but, well).
- 便携性较差(但是,好吧)。
Method 2. Using a “reference” variable
方法 2. 使用“引用”变量
Since Bash 4.3 (released 2014), the declare
builtin has an option -n
for creating a variable which is a “name reference” to another variable, much like C++ references. Just as in Method 1, the reference stores the name of the aliased variable, but each time the reference is accessed (either for reading or assigning), Bash automatically resolves the indirection.
从 Bash 4.3(2014 年发布)开始,declare
内置-n
函数有一个选项来创建一个变量,它是另一个变量的“名称引用”,很像 C++ 引用。与方法 1 一样,引用存储别名变量的名称,但每次访问引用(读取或赋值)时,Bash 都会自动解析间接引用。
In addition, Bash has a special and very confusing syntax for getting the value of the reference itself, judge by yourself: ${!ref}
.
另外,Bash 获取引用本身的值有一种特殊且非常混乱的语法,请自行判断:${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
This does not avoid the pitfalls explained below, but at least it makes the syntax straightforward.
这并不能避免下面解释的陷阱,但至少它使语法变得简单。
Downsides:
缺点:
- Not portable.
- 不便携。
Risks
风险
All these aliasing techniques present several risks. The first one is executing arbitrary code each time you resolve the indirection (either for reading or for assigning). Indeed, instead of a scalar variable name, like var_37
, you may as well alias an array subscript, like arr[42]
. But Bash evaluates the contents of the square brackets each time it is needed, so aliasing arr[$(do_evil)]
will have unexpected effects… As a consequence, only use these techniques when you control the provenance of the alias.
所有这些混叠技术都存在一些风险。第一个是每次解析间接寻址(用于读取或分配)时执行任意代码。实际上,var_37
除了标量变量名称,例如,您还可以将数组下标作为别名,例如arr[42]
。但是 Bash 每次需要时都会评估方括号的内容,因此别名arr[$(do_evil)]
会产生意想不到的效果......因此,只有在控制别名的出处时才使用这些技术。
function guillemots() {
declare -n var=""
var="?${var}?"
}
arr=( aaa bbb ccc )
guillemots 'arr[1]' # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]' # writes twice into date.out
# (once when expanding var, once when assigning to it)
The second risk is creating a cyclic alias. As Bash variables are identified by their name and not by their scope, you may inadvertently create an alias to itself (while thinking it would alias a variable from an enclosing scope). This may happen in particular when using common variable names (like var
). As a consequence, only use these techniques when you control the name of the aliased variable.
第二个风险是创建循环别名。由于 Bash 变量是由它们的名称而不是它们的作用域来标识的,因此您可能会无意中为其自身创建一个别名(同时认为它会为封闭作用域中的变量设置别名)。尤其是在使用公共变量名称(如var
)时,可能会发生这种情况。因此,只有在您控制别名变量的名称时才使用这些技术。
function guillemots() {
# var is intended to be local to the function,
# aliasing a variable which comes from outside
declare -n var=""
var="?${var}?"
}
var='lolilol'
guillemots var # Bash warnings: “var: circular name reference”
echo "$var" # outputs anything!
Source:
来源:
回答by Meow Meow
Example below returns value of $name_of_var
下面的示例返回 $name_of_var 的值
var=name_of_var
echo $(eval echo "$$var")
回答by Jahid
This should work:
这应该有效:
function grep_search() {
declare magic_variable_="$(ls | tail -1)"
echo "$(tmpvar=magic_variable_ && echo ${!tmpvar})"
}
grep_search var # calling grep_search with argument "var"
回答by k_vishwanath
This will work too
这也行
my_country_code="green"
x="country"
eval z='$'my_"$x"_code
echo $z ## o/p: green
In your case
在你的情况下
eval final_val='$'magic_way_to_define_magic_variable_""
echo $final_val
回答by ingyhere
Wow, most of the syntax is horrible! Here is one solution with some simpler syntax if you need to indirectly reference arrays:
哇,大部分语法都太可怕了!如果您需要间接引用数组,这是一种具有更简单语法的解决方案:
#!/bin/bash
foo_1=("fff" "ddd") ;
foo_2=("ggg" "ccc") ;
for i in 1 2 ;
do
eval mine=( ${foo_$i[@]} ) ;
echo ${mine[@]} ;
done ;
For simpler use cases I recommend the syntax described in the Advanced Bash-Scripting Guide.
对于更简单的用例,我推荐使用Advanced Bash-Scripting Guide 中描述的语法。
回答by kenorb
As per BashFAQ/006, you can use read
with here string syntaxfor assigning indirect variables:
按照BashFAQ / 006,您可以使用read
与此字符串语法分配间接变量:
function grep_search() {
read "" <<<$(ls | tail -1);
}
Usage:
用法:
$ grep_search open_box
$ echo $open_box
stack-overflow.txt
回答by laconbass
Use declare
用 declare
There is no need on using prefixes like on other answers, neither arrays. Use just declare
, double quotes, and parameter expansion.
不需要像其他答案一样使用前缀,也不需要使用数组。使用 just declare
、双引号和参数扩展。
I often use the following trick to parse argument lists contanining one to n
arguments formatted as key=value otherkey=othervalue etc=etc
, Like:
我经常使用以下技巧来解析包含one to n
格式为 的参数的参数列表key=value otherkey=othervalue etc=etc
,例如:
# brace expansion just to exemplify
for variable in {one=foo,two=bar,ninja=tip}
do
declare "${variable%=*}=${variable#*=}"
done
echo $one $two $ninja
# foo bar tip
But expanding the argv list like
但是像这样扩展 argv 列表
for v in "$@"; do declare "${v%=*}=${v#*=}"; done
Extra tips
额外提示
# parse argv's leading key=value parameters
for v in "$@"; do
case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
done
# consume argv's leading key=value parameters
while (( $# )); do
case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
shift
done
回答by jpbochi
An extra method that doesn't rely on which shell/bash version you have is by using envsubst
. For example:
不依赖于您拥有哪个 shell/bash 版本的额外方法是使用envsubst
. 例如:
newvar=$(echo '$magic_variable_'"${dynamic_part}" | envsubst)