我知道在 shell 脚本中,“退出”通常意味着自愿或至少成功终止一个会话(或一个过程在一个会话内)并且有几种不同的退出模式;以下是我所知道的:
1. 一个简单的exit
命令
如果我处于第一个 shell 会话 (shell-session 0),通常会导致 shell CLI窗户关闭,但如果我处于某个子会话(如 shell-session 1 或更高版本)中,执行通常只会将我的用户移回到上一个会话(例如1 → 0
)。
2.exit SOME_EXIT-CODE
命令
我发现三个主要退出代码用于此类退出:
exit 0
(成功)。exit 1
(一般错误,例如“除以零”和其他不允许的操作)。exit 2
(如 Bash 4.xx 中 - 滥用 shell 内置函数,一个例子是空函数;myFunc() {}
)。
我经常发现它们添加到命令序列的末尾作为其执行的指示器结果;有时作为单元测试,如:
domain="$1" && test -z "$domain" && exit 2
# Test if a user passes only one domain as a parameter, when executing this script
3.非添加脚本退出
如果我没记错的话,当 Bash 脚本结束运行时,它的“结束”实际上是常见 *nix 术语中的“退出”——脚本本身是一个会话,用户可以从该会话退出回到 CLI 会话。这里也有一些退出代码,可能会被给出。
我的问题
一般来说,在 shell 脚本中,特别是在 Bash 中,是否还有其他“退出模式”?
答案1
“退出”通常意味着自愿或至少成功终止
至少 POSIX 文本似乎使用出口仅用于自愿终止进程,而不是因外部原因被杀死。 (参见例如wait()
)被信号杀死的进程很难算作成功,因此任何成功的终止在这个意义上都必须是“退出”。尽管我希望这些术语在非正式使用中的使用不那么严格。
一般来说,在 shell 脚本中,特别是在 Bash 中,是否还有其他“退出模式”?
模式在某些情况下具有特定的技术含义(例如chmod()
),但我在这里想不出来,所以我不太确定你在问什么。
无论如何,shell 脚本可能至少由于以下原因而退出终止:
- 脚本运行到脚本结束。脚本的退出状态是最后执行的命令的退出状态。
- 该脚本运行
exit
内置命令没有争论。同样,退出状态是最后执行的命令的状态。 - 该脚本运行
exit
带有参数的命令。退出状态是参数的值。 - 该脚本引用了一个未设置的变量
set -u
/set -o nounset
已生效。退出状态取决于 shell,但不为零。 (Bash 似乎使用127
。)(*) - 该脚本运行一个失败的命令
set -e
/set -o errexit
已生效。退出状态是失败命令的退出状态。(但请参阅Bash常见问题解答 105对于问题set -e
。) - 该脚本遇到语法错误。 shell 的退出状态不为零。 (Bash 似乎使用
1
。)(*) - 脚本接收到一个信号这会导致它终止。并非所有信号都会导致终止,信号可以被忽略,也可以使用以下命令在脚本中设置处理程序
trap
内置命令。这也适用于Ctrl-C发送SIGINT
信号的 eq hit 。(*)
从技术意义上来说,在情况 1 至 6 中,壳进程运行脚本会自动退出(即进程调用exit()
)。另一方面,从对方的角度来看脚本本身,由于 或语法错误而终止set -e
很set -u
可能被称为非自愿的。但shell脚本与shell解释器不一样。
在 1 到 3 中,习惯是使用零退出状态表示成功完成,使用非零值表示失败。非零值的确切含义取决于实用程序。有些可能仅使用零和一,有些可能针对不同情况使用不同的非零状态。例如,grep
使用1
表示未找到匹配项,值大于1
表示有错误。 Bash 的内置函数还用于2
指示无效选项等错误。使用类似的自定义可能很有用,但您需要记录脚本的退出状态的含义。请注意,退出状态通常限制为 8 位,因此范围是从0
到255
。
在 4 到 6 中,这种情况通常被认为是某种故障,因此退出状态不为零。 7、没有退出状态。相反,当进程因信号而终止时,wait()
系统调用会指示有问题的信号。如果父进程是一个 shell,它通常用退出状态 来表示128 + <signal number>
,例如143
对于以 终止的子进程SIGTERM
。
(* 与脚本不同,交互式 shell 不会因语法错误 或set -u
或 而退出SIGINT
。)
如果我处于第一个 shell 会话中,通常会导致 shell CLI 窗口关闭
如果终端仿真器启动的进程退出,它通常会关闭。但这取决于终端仿真器,而不是 shell 的功能。终端仿真器可能决定保持窗口打开,以告诉用户他们的程序已终止,并且您也可以在终端仿真器中运行 shell 以外的其他东西。
如果我处于某个子会话中,执行通常会将我的用户移回到上一个会话。
如果您使用交互式 shell 启动另一个 shell,则当子 shell 终止时,父 shell 会继续运行。但同样,这与 shell 无关,如果您启动编辑器或仅从交互式 shell 运行任何命令,也会发生同样的情况。当子进程终止时,父 shell 继续接受来自用户的命令。
Bash 确实保留了一个变量SHLVL
,每次 Bash 启动时该变量都会加一,因此从某种意义上说,它确实具有嵌套 shell 的内部思想。但我认为“子会议”这个词并不常见,更不用说任何形式的编号了。 (我认为SHLVL
初始化于1
。)
答案2
退出的“模式”并没有真正不同。
不带参数使用exit
将使用前一个命令的返回码作为返回码。如果这是脚本中的最后一个命令,则它实际上是一个 NOOP,除非正在source
编写脚本。
exit 0
显式地将返回代码设置为0
,即非错误状态的标准退出代码。
使用 0 以外的数字只是做同样的事情:将返回码设置为指定值。
当脚本在没有exit
指令的情况下结束时,将使用该命令执行的最终命令的退出代码。
如果您在陷阱中trap '[...]' EXIT
包含一条语句,则它将取代该语句,因为陷阱将在脚本实际终止之前执行(包括任何包含的命令)。exit
exit
答案3
除了 0、1 和 2 之外,退出代码还有很多。请参阅:
考虑退出代码时,您应该记住脚本并不总是在您可能怀疑的地方结束。在它们完成之前,它们可能会被一个信号杀死,或者它们可能会由于按Ctrl-的意外输入终止符而结束D,从而产生意外的退出。因此,需要对脚本有很好的理解和分析,尤其是那些返回大量代码的脚本。
答案4
我不确定我是否正确理解了这个问题,但你似乎在寻求程序的不同方式终止代替退出?
您可以像您所说的那样让 shell(或 shell 运行的命令)退出(例如:如果您的交互式 shell 遇到 EoF,它将退出。如果任何 shell 运行完要运行的命令(脚本结束),它也会退出,或者如果遇到退出(例如:shell 中的“exit 2”使其退出并返回代码 2)
如果父 shell 退出并且没有运行“nohup”(或其变体)来将进程与该父 shell 分离,您可能会收到“HUP”
您可能会收到 Ctrl-C
你可能会耗尽内存/CPU/其他东西(fd?)
你可能会收到一个kill -9或其他(脚本本身可以这样“kill -9 $$”本身,即使它很奇怪......)
您的外壳可能位于管道的右侧,该管道由于左端突然终止而破裂
我敢打赌还有更多的案例......
其中每一个都应该终止程序/shell,并且每一个都有点不同(并且您可以捕获其中一些在退出之前执行某些操作,或者完全阻止退出(对于 ctrl-C,例如,您可以捕获 INT 信号) )