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