Bash中的动态变量名称

问题描述 投票:114回答:11

我对bash脚本感到困惑。

我有以下代码:

function grep_search() {
    magic_way_to_define_magic_variable_$1=`ls | tail -1`
    echo $magic_variable_$1
}

我希望能够创建一个包含命令的第一个参数并带有例如值的变量名。 ls的最后一行。

所以说明我想要的东西:

$ ls | tail -1
stack-overflow.txt

$ grep_search() open_box
stack-overflow.txt

那么,我应该如何定义/声明$magic_way_to_define_magic_variable_$1以及如何在脚本中调用它?

我曾尝试过qazxsw poi,qazxsw poi,qazxsw poi,但我仍感到困惑。

bash variables dynamic syntax
11个回答
124
投票

使用关联数组,命令名称作为键。

eval

如果你不能使用关联数组(例如,你必须支持${...} 3),你可以使用\$${...}来创建动态变量名:

# Requires bash 4, though
declare -A magic_variable=()

function grep_search() {
    magic_variable[$1]=$( ls | tail -1 )
    echo ${magic_variable[$1]}
}

并使用间接参数扩展来访问该值。

bash

见BashFAQ:declare


0
投票

根据${!var},你可以使用BashFAQ/006 about Indirectioneval来分配间接变量:

BashFAQ/006

用法:

read

-2
投票

对于here string syntax格式,只需使用:

function grep_search() {
  read "$1" <<<$(ls | tail -1);
}

194
投票

我最近一直在寻找更好的方法。关联数组听起来像是矫枉过正。看我发现了什么:

declare "magic_variable_$1=$(ls | tail -1)"

...然后...

var="magic_variable_$1"
echo "${!var}"

16
投票

以下示例返回$ name_of_var的值

Indirection - Evaluating indirect/reference variables

5
投票

除了关联数组之外,还有几种在Bash中实现动态变量的方法。请注意,所有这些技术都存在风险,本答案最后将对此进行讨论。

在下面的例子中,我将假设suffix=bzz declare prefix_$suffix=mystr 并且您想要为名为varname=prefix_$suffix echo ${!varname} 的变量设置别名,该变量的初始值为var=name_of_var echo $(eval echo "\$$var")

Method 1. Using a “pointer” variable

您可以简单地将变量的名称存储在间接变量中,与C指针不同。然后Bash有一个读取别名变量的语法:i=37扩展为变量的值,该变量的名称是变量var_37的值。您可以将其视为两阶段扩展:lolilol扩展到${!name},扩展到name

${!name}

不幸的是,没有用于修改别名变量的对应语法。相反,您可以使用以下技巧之一来完成作业。

1a. Assigning with $var_37

lolilol是邪恶的,但也是实现我们目标的最简单,最便携的方式。你必须小心地逃避任务的右侧,因为它将被评估两次。一个简单而系统的方法是事先评估右侧(或使用name="var_$i" echo "$name" # outputs “var_37” echo "${!name}" # outputs “lolilol” echo "${!name%lol}" # outputs “loli” # etc. )。

你应该手动检查左侧是有效的变量名称,还是带索引的名称(如果是eval会怎样?)。相比之下,以下所有其他方法都会自动执行。

eval

缺点:

  • 不检查变量名称的有效性。
  • printf %q是邪恶的。
  • 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” 是邪恶的。

1b. Assigning with eval

eval builtin允许您为给出名称的变量赋值,这个事实可以与here-strings一起使用:

eval

read部分和选项read确保该值按原样分配,而选项IFS= read -r -d '' "$name" <<< 'babibab' echo "$var_37" # outputs “babibab\n” 允许分配多行值。由于这最后一个选项,该命令返回非零退出代码。

请注意,由于我们使用的是here-string,因此会在该值附加换行符。

缺点:

  • 有点模糊;
  • 返回非零退出代码;
  • 在值上附加换行符。

1c. Assigning with IFS

自Bash 3.1(2005年发布)以来,-r内置函数也可以将其结果赋给给定名称的变量。与之前的解决方案相比,它只是起作用,不需要额外的努力来逃避事情,防止分裂等等。

-d ''

缺点:

  • 便携性较差(但很好)。

Method 2. Using a “reference” variable

自Bash 4.3(2014年发布)以来,printf内置有一个选项printf用于创建一个变量,它是另一个变量的“名称引用”,就像C ++引用一样。与方法1中一样,引用存储别名变量的名称,但每次访问引用(用于读取或分配)时,Bash都会自动解析间接。

另外,Bash有一个特殊且非常混乱的语法来获取引用本身的值,由你自己判断:printf -v "$name" '%s' 'babibab' echo "$var_37" # outputs “babibab”

declare

这并不能避免下面解释的陷阱,但至少它使语法简单明了。

缺点:

  • 不便携。

Risks

所有这些混叠技术都存在一些风险。第一个是每次解析间接时执行任意代码(用于读取或分配)。实际上,您可以使用像-n这样的数组下标来代替标量变量名称,例如${!ref}。但是Bash每次需要时都会评估方括号的内容,因此别名declare -n ref="var_$i" echo "${!ref}" # outputs “var_37” echo "$ref" # outputs “lolilol” ref='babibab' echo "$var_37" # outputs “babibab” 会产生意想不到的效果......因此,只有在控制别名的出处时才使用这些技术。

var_37

第二个风险是创建循环别名。由于Bash变量是由它们的名称而不是它们的范围来标识的,因此您可能无意中为自己创建了一个别名(同时认为它会将来自封闭范围的变量设为别名)。特别是在使用常见变量名称(如arr[42])时,可能会发生这种情况。因此,只有在控制别名变量的名称时才使用这些技术。

arr[$(do_evil)]

资源:

  • function guillemots() { declare -n var="$1" 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)
  • var

4
投票

这应该工作:

function guillemots() {
  # var is intended to be local to the function,
  # aliasing a variable which comes from outside
  declare -n var="$1"
  var="«${var}»"
}

var='lolilol'
guillemots var  # Bash warnings: “var: circular name reference”
echo "$var"     # outputs anything!


2
投票

哇,大部分语法都太可怕了!如果您需要间接引用数组,这是一个具有一些更简单语法的解决方案:

function grep_search() {
    declare magic_variable_$1="$(ls | tail -1)"
    echo "$(tmpvar=magic_variable_$1 && echo ${!tmpvar})"
}
grep_search var  # calling grep_search with argument "var"

对于更简单的用例,我推荐使用my_country_code="green" x="country" eval z='$'my_"$x"_code echo $z ## o/p: green


1
投票

对于索引数组,您可以像这样引用它们:

eval final_val='$'magic_way_to_define_magic_variable_"$1"
echo $final_val

关联数组可以类似地引用,但需要在#!/bin/bash foo_1=("fff" "ddd") ; foo_2=("ggg" "ccc") ; for i in 1 2 ; do eval mine=( \${foo_$i[@]} ) ; echo ${mine[@]} ; done ; 而不是syntax described in the Advanced Bash-Scripting Guide上使用foo=(a b c) bar=(d e f) for arr_var in 'foo' 'bar'; do declare -a 'arr=("${'"$arr_var"'[@]}")' # do something with $arr echo "\$$arr_var contains:" for char in "${arr[@]}"; do echo "$char" done done 开关。


0
投票

我希望能够创建一个包含命令的第一个参数的变量名称

-A文件:

declare

测试:

-a

根据script.sh

执行参数作为shell命令。


如前所述,您也可以使用Bash #!/usr/bin/env bash function grep_search() { eval $1=$(ls | tail -1) } 间接扩展,但它不支持检索数组索引。


如需进一步阅读或示例,请查看$ source script.sh $ grep_search open_box $ echo $open_box script.sh

我们不知道任何可以在没有help eval的情况下复制POSIX或Bourne shell中的功能的技巧,这可能很难安全地执行。所以,考虑一下这个用户自担风险。

但是,您应该根据以下注释重新考虑使用间接。

通常,在bash脚本中,您根本不需要间接引用。一般来说,当人们不了解或了解Bash Arrays或者没有充分考虑其他Bash功能(如函数)时,人们会考虑这个解决方案。

将变量名或任何其他bash语法放在参数中经常是错误的,并且在不适当的情况下解决具有更好解决方案的问题。它违反了代码和数据之间的分离,因此使您陷入了错误和安全问题的滑坡。间接可以使您的代码不那么透明,更难以遵循。

© www.soinside.com 2019 - 2024. All rights reserved.