我有一个脚本,当我想要它时它不会退出。
具有相同错误的示例脚本是:
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
我假设看到输出:
:~$ ./test.sh
1
:~$
但我实际上看到:
:~$ ./test.sh
1
2
:~$
命令链接是否()
以某种方式创建了范围?exit
如果不是脚本,退出的是什么呢?
答案1
()
在子 shell 中运行命令,因此exit
您可以退出子 shell 并返回到父 shell。{}
如果要在当前 shell 中运行命令,请使用大括号。
来自 bash 手册:
(列表)list 在子shell 环境中执行。影响 shell 环境的变量分配和内置命令在命令完成后不再有效。返回状态是list的退出状态。
{ 列表; }list 只是在当前 shell 环境中执行。列表必须以换行符或分号结束。这称为组命令。返回状态是list的退出状态。请注意,与元字符 ( 和 ) 不同,{ 和 } 是保留字,并且必须出现在允许识别保留字的位置。由于它们不会导致断词,因此必须用空格或其他 shell 元字符将它们与列表分隔开。
值得一提的是,shell 语法非常一致,并且子 shell 还参与其他()
构造,例如命令替换(也使用旧式`..`
语法)或进程替换,因此以下内容也不会从当前 shell 退出:
echo $(exit)
cat <(exit)
虽然很明显,当命令显式放置在 内部时()
,会涉及到子 shell,但不太明显的事实是,它们也会在这些其他结构中生成:
命令在后台启动
exit &
不退出当前 shell 因为(之后
man bash
)如果命令由控制运算符 & 终止,则 shell 会在子 shell 的后台执行该命令。 shell 不等待命令完成,返回状态为 0。
管道
exit | echo foo
仍然仅从子 shell 退出。
但是不同的 shell 在这方面的行为不同。例如,
bash
将管道的所有组件放入单独的子 shell 中(除非您lastpipe
在未启用作业控制的调用中使用该选项),但 AT&Tksh
并zsh
在当前 shell 中运行最后一部分(POSIX 允许这两种行为)。因此exit | exit | exit
在 bash 中基本上不执行任何操作,但由于以下原因退出 zsh最后
exit
。coproc exit
也运行exit
在子shell中。
答案2
在子 shell 中执行exit
是一个陷阱:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
脚本打印42,退出子外壳带有返回代码1
,并继续执行脚本。即使将调用替换为 也echo $(CALC) || exit 1
无济于事,因为echo
无论 的返回码如何, 的返回码都是 0 calc
。并且calc
在 之前执行echo
。
exit
更令人费解的是,通过将其包装到内置脚本中来阻止效果local
,如以下脚本所示。当我编写一个函数来验证输入值时,我偶然发现了这个问题。例子:
我想创建一个名为“年月日.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 中检测到错误的输入数据。但exit 1
行尾的local …
永远不会被触发,因为local
指令总是返回0
。这是因为local
被执行后 $(fname)
从而覆盖其返回码。因此,脚本会继续并touch
使用空参数进行调用。这个示例很简单,但 bash 的行为在实际应用程序中可能会非常混乱。我知道,真正的程序员不会使用当地人。☺
明确地说:如果没有local
,当输入无效日期时,脚本将按预期中止。
修复方法是将线分开
local FNAME
FNAME=$(fname "$1") || exit 1
这种奇怪的行为符合local
bash 手册页中的文档:“返回状态为 0,除非在函数外部使用 local、提供了无效名称或 name 是只读变量。”
虽然不是一个错误,但我觉得 bash 的行为是违反直觉的。不过,我知道执行的顺序,local
不应该掩盖损坏的分配。
我最初的回答包含一些不准确之处。在与 mikeserv 进行了深入而深入的讨论(谢谢你)后,我开始修复它们。
答案3
实际解决方案:
#!/bin/bash
function bla() {
return 1
}
bla || { echo '1'; exit 1; }
echo '2'
错误分组仅在bla
返回错误状态时才会执行,并且exit
不在子 shell 中,因此整个脚本都会停止。
答案4
括号开始一个子外壳并且退出仅退出该子 shell。
您可以使用以下命令读取退出代码$?
并将其添加到脚本中,以便在退出子 shell 时退出脚本:
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
exitcode=$?
if [ $exitcode != 0 ]; then exit $exitcode; fi
echo '2'