我正在编写一个在BASH脚本中使用的配置文件解析器。它具有变量赋值的基本shell语法,但也有一些额外的验证以避免意外误用。一切都在一个成功的场景中运行。然而,当eval失败时,不执行处理失败的方法。我不知道为什么不能处理。下面我做了一个简化的例子来演示我遇到的问题。
#!/usr/bin/env bash
shopt -s -o nounset
shopt -s -o errexit
shopt -s -o pipefail
shopt -s globstar
shopt -s extglob
#shopt -s -o xtrace
IFS=$'\n\t'
function loadConf()
{
local confFileLcl="$1"
local lineLcl
local optionLcl
local valueLcl
while IFS='' read -r lineLcl
do
lineLcl=${lineLcl%%#*}
case "${lineLcl}" in
*=*)
optionLcl="${lineLcl%%=*}"
valueLcl=${lineLcl#*=}
valueLcl=${valueLcl//\"/}
eval "${optionLcl}='${valueLcl}'" || echo "Failed to set '${optionLcl}'."
esac
done <"${confFileLcl}"
}
declare -frt loadConf
# Create configuration file
echo -e "
var0=\"Value is set\"
var1=\"This is not an integer\"
" > "test.conf"
# Create variable that is to be configured to succeed.
declare var0="not-set"
# Create variable that is to be configured to fail.
declare -i var1=42
# Load the configuration file
loadConf "test.conf"
[[ "${var0}" == "Value is set" ]] || echo "Var0 check failed."
[[ "${var1}" -eq 42 ]] || echo "Var1 check failed."
字符串
我已经尝试了各种方法来缩小问题的范围,包括禁用启用的严格模式。这并不会对eval失败处理产生影响,只是打印的错误消息的类型不同。
执行输出:
$ ./demo.sh
./demo.sh:line 27:This:非约束变量
第27行是:eval“${optionLcl}='${valueLcl}'”||{ echo“无法设置“${optionLcl}”。”; true;
我对输出的期望是:
无法设置“var 1”。
Var 1检查失败。
使用跟踪运行:
$ ./demo.sh
+ IFS='
'
+ declare -frt loadConf
+ echo -e '
var0="Value is set"
var1="This is not an integer"
'
+ declare var0=not-set
+ declare -i var1=42
+ loadConf test.conf
+ local confFileLcl=test.conf
+ local lineLcl
+ local optionLcl
+ local valueLcl
+ IFS=
+ read -r lineLcl
+ lineLcl=
+ case "${lineLcl}" in
+ IFS=
+ read -r lineLcl
+ lineLcl='var0="Value is set"'
+ case "${lineLcl}" in
+ optionLcl=var0
+ valueLcl='"Value is set"'
+ valueLcl='Value is set'
+ eval 'var0='\''Value is set'\'''
++ var0='Value is set'
+ IFS=
+ read -r lineLcl
+ lineLcl='var1="This is not an integer"'
+ case "${lineLcl}" in
+ optionLcl=var1
+ valueLcl='"This is not an integer"'
+ valueLcl='This is not an integer'
+ eval 'var1='\''This is not an integer'\'
++ var1='This is not an integer'
./demo.sh: line 27: This: unbound variable
型
2条答案
按热度按时间dnph8jn41#
现在,您的跟踪显示了正在发生的事情。如果你写的是
字符串
这里的问题是
optionLcl
扩展为var1
,并且您已经声明var1
为整数,这意味着右侧是在数值上下文中计算的。在这种情况下,是一个任务
型
被解释为(因为
foo
显然不是一个数字)型
这类似于
echo $((a+b))
,它被解释为echo $(($a + $b))
。但是没有名为foo
的变量(或者,在您的例子中,没有名为This
的变量,因为在您的原始程序中,赋值是 This is not an integer)。因为您还打开了 unset 选项,所以这会杀死您。问题仍然存在,为什么在
eval
遇到错误后不执行|| echo ...
分支。之后,一个简单的语法错误,如型
显示 error(除了来自
eval
的错误消息)。一些实验表明,numeric 上下文中的赋值行为似乎与我们预期的不同。范例:型
只显示语法错误,但不显示 error。赋值或参数替换似乎是关键点:
型
我不知道这种行为上的差异是bash的一些模糊但有文档记录的特性,还是仅仅是一个bug。
变通办法
因为你不能捕捉到将非数值的东西赋值给数值变量的错误(删除 unset 只会让这个错误不被发现,而拥有 unset 会中止你的程序),我的建议是将检查与实际赋值分开。你可以做个
型
第一个
eval
使用子shell,因此错误不会向上传播,除非通过子shell的退出代码,该代码由if
捕获。但是,由于eval发生在子进程中,因此脚本中不会设置任何变量。因此,我们在脚本的then
分支中重复eval
,因为我们现在知道我们是安全的。wgeznvg72#
nounset
选项与set -u
相同,并不意味着“导致对未设置变量求值的命令失败”。它的意思是“如果访问未设置的变量,则完全终止非交互式脚本”。所以没有其他命令有机会执行。交互式shell不会退出;在内部,它将执行类似于longjmp
的操作,以跳出任何递归并返回到提示符。解决这个问题的方法是检查正在设置的变量,如果没有设置,则避免
eval
。要检查是否设置了变量
var
,我们可以使用语法${var+y}
。如果设置了var
(可能为空,但已设置),则会扩展为y
。如果var
不存在,它将扩展为空。如果名称
var
保存在变量varname
中,我们可以用eval
来实现:字符串
我在这里假设
$varname
已经被验证为一个有效的变量名,这样的事情不需要引号。