mac*_*yle 8 bash shell-script function variable
我试图将“var name”传递给函数,让函数转换具有此类“var name”的变量包含的值,然后能够通过其原始“var name”引用转换后的对象。
例如,假设我有一个将分隔列表转换为数组的函数,并且我有一个名为“animal_list”的分隔列表。我想通过将列表名称传递给函数,然后将 now 数组引用为“animal_list”来将该列表转换为数组。
代码示例:
function delim_to_array() {
local list=$1
local delim=$2
local oifs=$IFS;
IFS="$delim";
temp_array=($list);
IFS=$oifs;
# Now I have the list converted to an array but it's
# named temp_array. I want to reference it by its
# original name.
}
# ----------------------------------------------------
animal_list="anaconda, bison, cougar, dingo"
delim_to_array ${animal_list} ","
# After this point I want to be able to deal with animal_name as an array.
for animal in "${animal_list[@]}"; do
echo "NAME: $animal"
done
# And reuse this in several places to converted lists to arrays
people_list="alvin|baron|caleb|doug"
delim_to_array ${people_list} "|"
# Now I want to treat animal_name as an array
for person in "${people_list[@]}"; do
echo "NAME: $person"
done
Run Code Online (Sandbox Code Playgroud)
小智 9
理解这一点需要一些努力。要有耐心。该解决方案将在 bash 中正常工作。需要一些“bashims”。
第一:我们需要使用“间接”访问变量${!variable}。如果$variable包含字符串animal_name,“参数扩展”:${!variable}将扩展到 的内容$animal_name。
让我们看看这个想法在行动,我尽可能保留了您使用的名称和值,以便您更容易理解:
#!/bin/bash
function delim_to_array() {
local VarName=$1
local IFS="$2";
printf "inside IFS=<%s>\n" "$IFS"
echo "inside var $VarName"
echo "inside list = ${!VarName}"
echo a\=\(${!VarName}\)
eval a\=\(${!VarName}\)
printf "in <%s> " "${a[@]}"; echo
eval $VarName\=\(${!VarName}\)
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "out <%s> " "${animal_list[@]}"; echo
printf "outside IFS=<%s>\n" "$IFS"
# Now we can use animal_name as an array
for animal in "${animal_list[@]}"; do
echo "NAME: $animal"
done
Run Code Online (Sandbox Code Playgroud)
如果执行该完整脚本(假设其名为 so-setvar.sh),您应该看到:
$ ./so-setvar.sh
inside IFS=<,>
inside var animal_list
inside list = anaconda, bison, cougar, dingo
a=(anaconda bison cougar dingo)
in <anaconda> in <bison> in <cougar> in <dingo>
out <anaconda> out <bison> out <cougar> out <dingo>
outside IFS=<
>
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo
Run Code Online (Sandbox Code Playgroud)
理解“内部”意味着“功能内部”,而“外部”则相反。
里面的值$VarName是 var: 的名称animal_list,作为一个字符串。
的值${!VarName}显示为列表:anaconda, bison, cougar, dingo
现在,为了展示解决方案的构建方式,有一行带有 echo:
echo a\=\(${!VarName}\)
Run Code Online (Sandbox Code Playgroud)
这显示了以下行eval执行的内容:
a=(anaconda bison cougar dingo)
Run Code Online (Sandbox Code Playgroud)
一旦被评估,变量a就是一个带有动物列表的数组。在这种情况下,var a 用于准确显示 eval 如何影响它。
然后,每个元素的值a打印为<in> val。
同样在函数的外部执行,如<out> val
这两行所示:
in <anaconda> in <bison> in <cougar> in <dingo>
out <anaconda> out <bison> out <cougar> out <dingo>
Run Code Online (Sandbox Code Playgroud)
请注意,真正的更改是在函数的最后一个 eval 中执行的。
就这样,大功告成。var 现在有一个值数组。
其实函数的核心就是一行: eval $VarName\=\(${!VarName}\)
此外,IFS 的值被设置为函数的本地值,这使其返回到执行函数之前的值,而无需任何额外工作。感谢Peter Cordes对原始想法的评论。
解释到此结束,希望它清楚。
如果我们删除所有不需要的行,只留下核心 eval,只为 IFS 创建一个新变量,我们将函数减少到它的最小表达式:
delim_to_array() {
local IFS="${2:-$' :|'}"
eval $1\=\(${!1}\);
}
Run Code Online (Sandbox Code Playgroud)
将 IFS 的值设置为局部变量,还允许我们为函数设置“默认”值。每当 IFS 所需的值没有作为第二个参数发送给函数时,本地 IFS 就会采用“默认”值。我觉得默认值应该是space( ) (这始终是一个有用的拆分值)、colon(:) 和vertical line(|)。这三个中的任何一个都会拆分值。当然,默认值可以设置为适合您需要的任何其他值。
read:为了降低 eval 中未引用值的风险,我们可以使用:
delim_to_array() {
local IFS="${2:-$' :|'}"
# eval $1\=\(${!1}\);
read -ra "$1" <<<"${!1}"
}
test="fail-test"; a="fail-test"
animal_list='bison, a space, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'
delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo
Run Code Online (Sandbox Code Playgroud)
$ so-setvar.sh
<bison>< a space>< {1..3}><~/><${a}><$a><$((2+2))><$(echo "fail")><./*><*><*>
Run Code Online (Sandbox Code Playgroud)
上面为 var 设置的大多数值animal_list都会因 eval 而失败。
但是通过阅读没有问题。
eval.为了真正理解这个函数是什么以及它是如何工作的,我重新编写了你使用这个函数发布的代码:
#!/bin/bash
delim_to_array() {
local IFS="${2:-$' :|'}"
# printf "inside IFS=<%s>\n" "$IFS"
# eval $1\=\(${!1}\);
read -ra "$1" <<<"${!1}";
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\t " "${animal_list[@]}"; echo
people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\t " "${people_list[@]}"; echo
Run Code Online (Sandbox Code Playgroud)
$ ./so-setvar.sh
NAME: anaconda NAME: bison NAME: cougar NAME: dingo
NAME: alvin NAME: baron NAME: caleb NAME: doug
Run Code Online (Sandbox Code Playgroud)
如您所见,IFS 仅在函数内部设置,不会永久更改,因此不需要将其重新设置为旧值。此外,该函数的第二次调用“people_list”利用了 IFS 的默认值,无需设置第二个参数。
在构造 (eval) 函数时,有一个地方可以将 var 不加引号地暴露给 shell 解析。这允许我们使用 IFS 值完成“分词”。但这也将 vars 的值(除非某些引用阻止)暴露为:“大括号扩展”、“波浪号扩展”、“参数、变量和算术扩展”、“命令替换”和“路径名扩展”,在那个命令。并<() >()在支持它的系统中进行过程替换。
每个(除了最后一个)的例子都包含在这个简单的回声中(小心):
a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*
Run Code Online (Sandbox Code Playgroud)
也就是说,任何{~$`<>以文件名开头或可能与文件名匹配或包含的字符串?*[]都是潜在问题。
如果您确定变量不包含此类有问题的值,那么您就是安全的。如果有可能拥有这样的值,那么回答您的问题的方法会更加复杂,需要更多(甚至更长)的描述和解释。使用read是另一种选择。
是的,read带有它自己的“龙”份额。
read命令可以得到只有一条线。多行,即使是通过设置-d选项,也需要特别小心。或者整个输入将分配给一个变量。IFSvalue 包含空格,则将删除前导和尾随空格。好吧,完整的描述应该包括一些关于 的细节tab,但我会跳过它。|读取数据。如果这样做, read 将位于子外壳中。在子 shell 中设置的所有变量在返回到父 shell 时不会持续存在。嗯,有一些解决方法,但是,我将再次跳过细节。我并不是要包括阅读的警告和问题,但应大众要求,我不得不包括它们,抱歉。
在简单的情况下,这eval是其他答案所建议的更好替代方案,这使得引用更容易。
func() { # set the caller's simple non-array variable
local retvar=$1
printf -v "$retvar" '%s ' "${@:2}" # concat all the remaining args
}
Run Code Online (Sandbox Code Playgroud)
Bash-completion(当你点击 tab 时运行的代码)已经切换到printf -v而不是eval为了它的内部函数,因为它更具可读性并且可能更快。
对于返回数组,Bash FAQ建议使用read -a读取数组变量的顺序数组索引:
# Bash
aref=realarray
IFS=' ' read -d '' -ra "$aref" <<<'words go into array elements'
Run Code Online (Sandbox Code Playgroud)
Bash 4.3 引入了一项功能,使按引用调用更加方便。Bash 4.3 仍然是新版本(2014)。
func () { # return an array in a var named by the caller
typeset -n ref1=$1 # ref1 is a nameref variable.
shift # remove the var name from the positional parameters
echo "${!ref1} = $ref1" # prints the name and contents of the real variable
ref1=( "foo" "bar" "$@" ) # sets the caller's variable.
}
Run Code Online (Sandbox Code Playgroud)
请注意,bash 手册页的措辞有点令人困惑。它说该-n属性不能应用于数组变量。这意味着您不能拥有引用数组,但可以拥有对数组的引用。