Bash中的动态变量名称

时间:2013-05-14 21:22:49

标签: bash variables dynamic syntax

我对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以及如何在脚本中调用它?

我尝试了eval${...}\$${...},但我仍感到困惑。

17 个答案:

答案 0 :(得分:189)

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

suffix=bzz
declare prefix_$suffix=mystr

......然后......

varname=prefix_$suffix
echo ${!varname}

答案 1 :(得分:118)

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

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

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

如果您不能使用关联数组(例如,您必须支持bash 3),则可以使用declare创建动态变量名称:

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

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

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

见BashFAQ:Indirection - Evaluating indirect/reference variables

答案 2 :(得分:16)

下面的示例返回$ name_of_var

的值
var=name_of_var
echo $(eval echo "\$$var")

答案 3 :(得分:4)

这应该有效:

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"

答案 4 :(得分:3)

将此处的两个高度评价的答案组合成一个完整的示例,希望该示例有用且不言自明:

#!/bin/bash

intro="You know what,"
pet1="cat"
pet2="chicken"
pet3="cow"
pet4="dog"
pet5="pig"

# Setting and reading dynamic variables
for i in {1..5}; do
        pet="pet$i"
        declare "sentence$i=$intro I have a pet ${!pet} at home"
done

# Just reading dynamic variables
for i in {1..5}; do
        sentence="sentence$i"
        echo "${!sentence}"
done

echo
echo "Again, but reading regular variables:"
echo $sentence1
echo $sentence2
echo $sentence3
echo $sentence4
echo $sentence5

输出:

你知道吗,我家里有一只宠物猫
你知道吗,我家里有一只宠物鸡
你知道吗,我家里有一只宠物牛
你知道吗,我家里有一只爱犬
你知道吗,我家里有一只宠物猪

同样,但读取常规变量:
你知道吗,我家里有一只宠物猫
你知道吗,我家里有一只宠物鸡
你知道吗,我家里有一只宠物牛
你知道吗,我家里有一只爱犬
你知道吗,我家里有一只宠物猪

答案 5 :(得分:3)

这也将起作用

my_country_code="green"
x="country"

eval z='$'my_"$x"_code
echo $z                 ## o/p: green

以您的情况

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

答案 6 :(得分:3)

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

在以下示例中,我将假设i=37,并且您要为变量var_37命名,该变量的初始值为lolilol

方法1.使用“指针”变量

您可以简单地将变量名存储在间接变量中,与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.

不幸的是,没有修改别名变量的对应语法。相反,您可以使用以下技巧之一实现分配。

1a。用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”

缺点:

  • 不检查变量名的有效性。
  • eval是邪恶的。
  • eval是邪恶的。
  • eval是邪恶的。

1b。用read

分配

内置的read可让您为变量指定值,并为其指定名称,这一事实可与here-strings一起使用:

IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37"  # outputs “babibab\n”

IFS部分和选项-r确保按原样分配值,而选项-d ''允许分配多行值。由于有最后一个选项,该命令将返回非零退出代码。

请注意,由于我们使用的是here字符串,因此在该值后附加了换行符。

缺点:

  • 有点晦涩;
  • 返回非零退出代码;
  • 在该值后添加换行符。

1c。用printf

分配

自Bash 3.1(2005年发行)以来,内置printf的结果也可以分配给名称已指定的变量。与以前的解决方案相比,它可以正常工作,不需要额外的工作来逃避事物,防止分裂等。

printf -v "$name" '%s' 'babibab'
echo "$var_37"  # outputs “babibab”

缺点:

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

方法2。使用“引用”变量

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

此外,Bash具有一种特殊且非常混乱的语法,用于获取引用本身的值,需要您自己判断:${!ref}

declare -n ref="var_$i"
echo "${!ref}"  # outputs “var_37”
echo "$ref"     # outputs “lolilol”
ref='babibab'
echo "$var_37"  # outputs “babibab”

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

缺点:

  • 不便携。

风险

所有这些别名技术都存在几种风险。第一个是每当您解决间接寻址时(读取或分配)执行任意代码。实际上,除了标量变量名称(如var_37)之外,您还可以为数组下标(如arr[42])加上别名。但是Bash每次都需要评估方括号的内容,因此别名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)

第二个风险是创建循环别名。由于Bash变量是通过它们的名称而不是它们的范围来标识的,因此您可能会无意中为其自身创建一个别名(同时认为它会在一个封闭范围内为一个变量添加别名)。使用公用变量名称(例如var)时,尤其可能发生这种情况。因此,仅在控制别名变量的名称时使用这些技术

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!

来源:

答案 7 :(得分:2)

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

#!/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

答案 8 :(得分:2)

对于 zsh(较新的 mac os 版本),您应该使用

real_var="holaaaa"
aux_var="real_var"
echo ${(P)aux_var}
holaaaa

代替“!”

答案 9 :(得分:1)

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

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

可以类似地引用关联数组,但需要-A上的declare开关而不是-a

答案 10 :(得分:1)

尽管这是一个老问题,但在避免使用eval(邪恶)命令的同时,获取动态变量名称仍然有些困难。

declare -n解决了该问题,该引用创建了对动态值的引用,这在CI / CD进程中特别有用,在CI / CD进程中,CI / CD服务的必需秘密名称直到运行时才知道。方法如下:

# Bash v4.3+
# -----------------------------------------------------------
# Secerts in CI/CD service, injected as environment variables
# AWS_ACCESS_KEY_ID_DEV, AWS_SECRET_ACCESS_KEY_DEV
# AWS_ACCESS_KEY_ID_STG, AWS_SECRET_ACCESS_KEY_STG
# -----------------------------------------------------------
# Environment variables injected by CI/CD service
# BRANCH_NAME="DEV"
# -----------------------------------------------------------
declare -n _AWS_ACCESS_KEY_ID_REF=AWS_ACCESS_KEY_ID_${BRANCH_NAME}
declare -n _AWS_SECRET_ACCESS_KEY_REF=AWS_SECRET_ACCESS_KEY_${BRANCH_NAME}

export AWS_ACCESS_KEY_ID=${_AWS_ACCESS_KEY_ID_REF}
export AWS_SECRET_ACCESS_KEY=${_AWS_SECRET_ACCESS_KEY_REF}

echo $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY
aws s3 ls

答案 11 :(得分:0)

亲吻方法:

a=1
c="bam"
let "$c$a"=4
echo bam1

结果为 4

答案 12 :(得分:0)

不依赖于您所使用的shell / bash版本的另一种方法是使用envsubst。例如:

newvar=$(echo '$magic_variable_'"${dynamic_part}" | envsubst)

答案 13 :(得分:0)

使用declare

不需要像其他答案一样使用前缀,也不需要使用数组。仅使用 declare 双引号参数扩展

我经常使用以下技巧来解析参数列表,这些参数列表包含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

但是像

那样扩展argv列表
for v in "$@"; do declare "${v%=*}=${v#*=}"; done

其他提示

# 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

答案 14 :(得分:0)

根据BashFAQ/006,您可以使用readhere string syntax分配间接变量:

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

用法:

$ grep_search open_box
$ echo $open_box
stack-overflow.txt

答案 15 :(得分:0)

  

我希望能够创建一个包含命令

的第一个参数的变量名

script.sh档案:

#!/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

  

将参数作为shell命令执行。

如前所述,您也可以使用Bash ${!var}间接扩展,但它不支持检索数组索引。

如需进一步阅读或示例,请查看BashFAQ/006 about Indirection

  

我们不知道任何可以在没有eval的POSIX或Bourne shell中复制该功能的技巧,这可能很难安全地执行。所以,认为这是一个自行冒险的用途

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

  

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

     

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

答案 16 :(得分:-2)

表示varname=$prefix_suffix格式,只需使用:

varname=${prefix}_suffix