我对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
,${...}
,\$${...}
,但我仍感到困惑。
答案 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
。
您可以简单地将变量名存储在间接变量中,与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.
不幸的是,没有修改别名变量的对应语法。相反,您可以使用以下技巧之一实现分配。
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
是邪恶的。 read
内置的read
可让您为变量指定值,并为其指定名称,这一事实可与here-strings一起使用:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
IFS
部分和选项-r
确保按原样分配值,而选项-d ''
允许分配多行值。由于有最后一个选项,该命令将返回非零退出代码。
请注意,由于我们使用的是here字符串,因此在该值后附加了换行符。
缺点:
printf
自Bash 3.1(2005年发行)以来,内置printf
的结果也可以分配给名称已指定的变量。与以前的解决方案相比,它可以正常工作,不需要额外的工作来逃避事物,防止分裂等。
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
缺点:
自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,您可以使用read
和here 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