为什么 ( exit 1 ) 不会退出脚本?
在子shell中执行exit是一个陷阱:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
脚本打印42,在返回代码为1的子shell中退出,并继续执行脚本。即使用echo $(CALC) || exit 1替换调用也无济于事,因为不论calc的返回代码如何,echo的返回代码始终为0。而且calc在echo之前执行。
更让人困惑的是通过将exit包装到内置的local中来阻止其效果,就像下面的脚本一样。我在编写一个验证输入值的函数时遇到了这个问题。例如:
我想创建一个名为"year month day.log"的文件,例如今天的20141211.log。日期由用户输入,但用户可能没有提供合理的值。因此,在我的fname函数中,我检查date的返回值以验证用户输入的有效性:
#!/bin/bash
doit ()
{
local FNAME=$(fname "$1") || exit 1
touch "${FNAME}"
}
fname ()
{
date +"%Y%m%d.log" -d"$1" 2>/dev/null
if [ "$?" != 0 ] ; then
echo "fname reports \"Illegal Date\"" >&2
exit 1
fi
}
doit "$1"
看起来不错。让脚本命名为 s.sh。如果用户使用 ./s.sh "Thu Dec 11 20:45:49 CET 2014" 调用脚本,将创建文件 20141211.log。然而,如果用户输入 ./s.sh "Thu hec 11 20:45:49 CET 2014",那么脚本将输出:
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
这行代码fname…表示在子shell中检测到了错误的输入数据。但是在local …行末尾的exit 1从来没有被触发,因为local指令总是返回0。这是因为local在执行$(fname)之后执行,并覆盖了它的返回码。因此,脚本继续执行并使用空参数调用touch。这个例子很简单,但是在实际应用中,Bash的行为可能会让人感到困惑。我知道,真正的程序员不使用局部变量。☺
为了明确起见:如果没有local,当输入无效日期时,脚本会按预期中止。
修复方法是将该行拆分为:
local FNAME
FNAME=$(fname "$1") || exit 1
奇怪的行为符合bash的man页中local的文档:“除非在函数外部使用local,提供一个无效的名称,或者name是只读变量,否则返回状态为0。”
虽然这不是一个错误,但我觉得bash的行为很反直觉。我知道执行顺序,local不应该掩盖一个有问题的赋值。
我的初始回答包含了一些不准确之处。在与mikeserv进行了深入讨论后(非常感谢),我决定对它们进行修正。