我有一个脚本,当我想要它时不会退出。
具有相同错误的示例脚本是:
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
我假设看到输出:
:~$ ./test.sh
1
:~$
但我实际上看到了:
:~$ ./test.sh
1
2
:~$
()
命令链是否以某种方式创建作用域?什么是exit
退出了,如果不是脚本?
我有一个脚本,当我想要它时不会退出。
具有相同错误的示例脚本是:
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
我假设看到输出:
:~$ ./test.sh
1
:~$
但我实际上看到了:
:~$ ./test.sh
1
2
:~$
()
命令链是否以某种方式创建作用域?什么是exit
退出了,如果不是脚本?
Answers:
()
在子Shell中运行命令,因此exit
您将退出子Shell并返回到父Shell。{}
如果要在当前Shell中运行命令,请使用花括号。
从bash手册:
(列表)列表在子shell环境中执行。在命令完成后,影响外壳环境的变量分配和内置命令将保持无效。返回状态是列表的退出状态。
{清单; } list仅在当前的shell环境中执行。列表必须以换行符或分号终止。这称为组命令。返回状态是列表的退出状态。请注意,与元字符(和)不同,{和}是保留字,并且必须出现在允许识别保留字的位置。由于它们不会造成单词中断,因此必须使用空格或其他Shell元字符将它们与list分开。
值得一提的是,shell语法非常一致,并且子shell也参与了其他()
结构,例如命令替换(也使用旧式`..`
语法)或进程替换,因此以下内容也不会退出当前的shell:
echo $(exit)
cat <(exit)
当将命令显式地放置在内部时()
,很可能会涉及到子外壳,但鲜为人知的事实是,它们也在以下其他结构中生成:
命令在后台启动
exit &
不会退出当前shell,因为(之后man bash
)
如果命令由控制操作符&终止,则外壳程序将在子外壳程序的后台执行该命令。Shell不等待命令完成,返回状态为0。
管道
exit | echo foo
仍然仅从子shell退出。
但是,不同的外壳在这方面的行为有所不同。例如bash
,将管道的所有组件放入单独的子外壳中(除非您lastpipe
在未启用作业控制的调用中使用该选项),而是将AT&T ksh
并zsh
在当前外壳中运行最后一部分(POSIX允许两种行为)。从而
exit | exit | exit
在bash中基本上不执行任何操作,但是由于last 退出zsh exit
。
coproc exit
也可以exit
在子外壳中运行。
{
和}
是不是语法,它们是保留字,必须由空间包围,且列表必须以命令终止(分号,换行符号)结束
(echo $$)
打印父Shell ID,因为$$
即使在创建子Shell之前它也会被扩展。事实上印刷子shell进程ID可能会非常棘手,看到stackoverflow.com/questions/9119885/...
$$
在创建子shell之前如何对其进行扩展,但仍$BASHPID
显示子shell的正确值?
exit
在子shell中执行是一个陷阱:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
脚本打印42,从子外壳退出并返回代码1
,然后继续执行脚本。即使用替换呼叫echo $(CALC) || exit 1
也无济于事,因为echo
无论返回码是,返回码都是0 calc
。并且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中检测到错误的输入数据。但是该行exit 1
末尾的local …
永远不会触发,因为该local
指令总是返回0
。这是因为在之后local
执行并因此覆盖了其返回代码。因此,脚本将继续并使用空参数进行调用。这个例子很简单,但是在实际的应用程序中,bash的行为可能会令人困惑。我知道,真正的程序员不使用本地语言。☺ $(fname)
touch
明确说明:如果没有local
,则输入无效日期时脚本将按预期中止。
解决办法是像
local FNAME
FNAME=$(fname "$1") || exit 1
奇怪的行为符合local
bash手册页中的文档:“返回状态为0,除非在函数外部使用local,提供了无效的名称或name为只读变量。”
虽然不是bug,但我认为bash的行为违反直觉。我知道执行的顺序local
,但是不应掩盖已损坏的任务。
我的最初答案包含一些错误。在与mikeserv进行了深入和深入的讨论之后(谢谢您),我去修复它们。
doit()
。