如何编写一个可靠地退出(以指定状态)当前进程的函数?

如何编写一个可靠地退出(以指定状态)当前进程的函数?

下面的脚本是问题的最小(尽管是人为的)说明。

## demo.sh

exitfn () {
    printf -- 'exitfn PID: %d\n' "$$" >&2
    exit 1
}

printf -- 'script PID: %d\n' "$$" >&2

exitfn | :

printf -- 'SHOULD NEVER SEE THIS (0)\n' >&2

exitfn

printf -- 'SHOULD NEVER SEE THIS (1)\n' >&2

在此示例脚本中,exitfn代表其工作需要终止当前进程1 的函数。

不幸的是,实施后exitfn并不能可靠地完成这一任务。

如果运行此脚本,输出如下所示:

% bash ./demo.sh
script PID: 26731
exitfn PID: 26731
SHOULD NEVER SEE THIS (0)
exitfn PID: 26731

(当然,每次调用时显示的 PID 值都会不同。)

这里的关键点是,在第一次调用该exitfn函数时,exit 1其主体中的命令无法终止封闭脚本的执行printf(正如紧随其后执行的第一个命令所证明的那样)。相反,在第二次调用 时exitfn,此exit 1命令确实会结束脚本的执行(第二个printf命令未执行的事实证明了这一点)。

两次调用之间的唯一区别exitfn在于,第一个调用作为双组件管道的第一个组件出现,而第二个调用是“独立”调用。

我对此感到困惑。我原以为这exit会导致杀死当前进程(即 PID 为 PID 的进程$$)。显然,这并不总是正确的。

尽管如此,有没有一种方法可以编写,exitfn以便即使在管道中调用它也可以退出周围的脚本?


顺便说一句,上面的脚本也是一个有效的 zsh 脚本,并产生相同的结果:

% zsh ./demo.sh
script PID: 26799
exitfn PID: 26799
SHOULD NEVER SEE THIS (0)
exitfn PID: 26799

我也对 zsh 这个问题的答案感兴趣。


最后,我应该指出,exitfn这样的实现根本不起作用:

exitfn () {
    printf -- 'exitfn PID: %d\n' "$$" >&2
    exit 1
    kill -9 "$$"
}

...因为,在任何情况下,该exit 1命令始终是执行该函数的最后一行。 (更换 exit 1withkill -9 $$是不可接受的:我想控制脚本的退出状态及其对 stderr 的输出。)


1实际上,此类函数会在终止当前进程之前执行其他任务,例如诊断日志记录或清理操作。

答案1

我原以为这exit会杀死当前进程(即具有由 给出的 PID 的进程$$

“当前进程”与“PID 给定的进程”不是一回事$$exit退出当前的子外壳,或者原始 shell(如果未在子 shell 中调用)。

某些构造,例如( … )(与子 shell 分组)、命令替换($(…)`…`)以及管道的每一侧(或某些 shell 中仅左侧)的内容,在子 shell 中运行。子 shell 的行为就好像它是使用以下命令创建的单独进程fork(),并且通常是这样实现的(某些 shell 在某些情况下不使用子进程,作为性能优化)。子shell有自己的变量副本、自己的重定向等。调用exit退出子shell,就像exit()标准C库中的函数退出进程一样。有关子 shell 的更多信息,请参阅什么是子 shell(在 make 文档的上下文中)?,“子shell”和“子进程”之间的确切区别是什么?$() 是子 shell 吗?

$$始终是原始 shell 进程的进程 ID。它在子 shell 中不会改变。某些 shell 具有在子 shell 中更改的变量,例如$BASHPID在 bash 和 mksh、${.sh.subshell}ksh 或$ZSH_SUBSHELLzsh$sysparams[pid]中。

有没有一种方法可以编写exitfn,以便即使在管道中调用它也可以退出周围的脚本?

不完全是。您需要做更多的工作,无论是在脚本创建子 shell 的地方,还是在顶层,或者两者兼而有之。看从子 shell 退出 shell 脚本用于近似值。


¹ 请注意,与其他 shell 相反,它zsh在父进程中的管道中执行扩展(从左到右),而不是在管道的每个成员中执行扩展:zsh -c 'echo $ZSH_SUBSHELL | cat'输出0(即使echo在子进程中运行)和zsh -c 'n=0; echo $((++n)) | echo $((++n))'输出 2。

相关内容