15

脚本1.sh:

 #!/bin/bash    

./script2.sh
 echo after-script

脚本2.sh:

#!/bin/bash

function handler {
  exit 130
}

trap handler SIGINT

while true; do true; done

当我从终端启动 script1.sh,然后使用Ctrl+C将 SIGINT 发送到其进程组时,信号被 script2.sh 捕获,当 script2.sh 终止时,script1.sh 打印“after-script”。但是,我希望 script1.sh 在调用 script2.sh 的行之后立即终止。为什么在这个例子中不是这种情况?

补充说明(编辑):

  • 由于 script1.sh 和 script2.sh 在同一个进程组中,当在命令行上按下+时,SIGINT 会被发送到两个脚本。这就是为什么我不希望 script1.sh 在 script2.sh 退出时继续。CtrlC

  • 当 script2.sh 中的“trap handler SIGINT”行被注释掉时,script1.sh 会在 script2.sh 存在后立即退出。我想知道为什么它的行为会有所不同,因为 script2.sh 会产生相同的退出代码(130)。

4

5 回答 5

14

新答案:

这个问题比我最初怀疑的要有趣得多。答案基本上在这里给出:

将 SIGINT (^C) 发送到包含子级的 perl 脚本时会发生什么情况?

这是相关的花絮。我意识到您没有使用 Perl,但我认为 Bash 使用的是 C 的约定。

就信号而言,Perl 的内置系统函数与标准 C 库中的 C system(3) 函数一样工作。如果您使用 Perl 的 system() 版本或管道打开或反引号,那么父系统(即调用系统而不是由它调用的系统)将在子系统运行时忽略任何 SIGINT 和 SIGQUIT。

这个解释是我所见过的关于可以做出的各种选择的最好的解释。它还说 Bash 采用 WCE 方法。也就是说,当父进程收到 SIGINT 时,它会一直等到其子进程返回。如果处理的该进程从 SIGINT 退出,它也会以 SIGINT 退出。如果孩子以任何其他方式退出,它将忽略 SIGINT。

还有一种方法可以让调用 shell 判断被调用程序是否在 SIGINT 上退出,以及它是否忽略了 SIGINT(或将其用于其他目的)。与 WUE 方式一样,shell 等待子进程完成。它计算程序是否在 SIGINT 上结束,如果是,则停止脚本。如果程序有任何其他退出,脚本将继续。在本文档的其余部分,我将把这种处理方式称为“WCE”(表示“等待和合作退出”)。

我在 Bash 手册页中找不到对此的引用,但我会继续查看信息文档。但我有 99% 的把握这是正确的答案。

老答案:

Bash 脚本中命令的非零退出状态不会终止程序。如果你做一个echo $?之后,它会显示 130。你可以按照 phs./script2.sh的建议使用终止脚本。set -e

$ help set
...
-e  Exit immediately if a command exits with a non-zero status.
于 2013-08-28T03:59:12.207 回答
2

@seanmcl 更新答案的第二部分是正确的,并且指向http://www.cons.org/cracauer/sigint.html的链接非常适合仔细阅读。

从该链接中,“即使您查找系统的数值,您也不能通过具有特殊数值的 exit(3) 来“伪造”正确的退出状态”。事实上,这就是@Hermann Speiche 的 script2.sh 所尝试的。

一种答案是修改 script2.sh 中的函数处理程序,如下所示:

function handler {
  # ... do stuff ...
  trap INT
  kill -2 $$
}

这有效地删除了信号处理程序并“重新抛出” SIGINT,导致 bash 进程以适当的标志退出,以便其父 bash 进程随后正确处理最初发送给它的 SIGINT。这样,set -e实际上不需要使用或任何其他 hack。

还值得注意的是,如果您的可执行文件在发送 SIGINT 时行为不正确(它不符合上述链接中的“如何成为正确的程序”,例如,它以正常的返回码退出),一种方法解决这个问题的方法是使用如下脚本包装对该进程的调用:

#!/bin/bash

function handler {
  trap INT
  kill -2 $$
}

trap handler INT
badprocess "$@"
于 2014-09-17T14:42:38.720 回答
0

原因是您script1.sh没有终止是script2.sh在子shell中运行。要退出前一个脚本,您可以-e按照 phs 和 seanmcl 的建议进行设置,也可以通过以下方式强制script2.sh运行在同一个 shell 中:

. ./script2.sh

在你的第一个脚本中。如果您要set -x在执行脚本之前执行此操作,那么您所观察到的将是显而易见的。 help set告诉:

  -x  Print commands and their arguments as they are executed.
于 2013-08-28T04:56:21.547 回答
0

您还可以让您的第二个脚本通过 SIGHUP 在其父脚本上发送终止信号,或其他安全且可用的信号(如 SIGQUIT),其中父脚本也可以考虑或捕获(发送 SIGINT 不起作用)。

脚本1.sh:

#!/bin/bash

trap 'exit 0' SIQUIT  ## We could also just accept SIGHUP if we like without traps but that sends a message to the screen.

./script2.sh  ## or "bash script.sh" or "( . ./script.sh; ) which would run it on another process
echo after-script

脚本2.sh:

#!/bin/bash

SLEEPPID=''

PID=$BASHPID
read PPID_ < <(exec ps -p "$PID" -o "$ppid=")

function handler {
  [[ -n $SLEEPPID ]] && kill -s SIGTERM "$SLEEPPID" &>/dev/null
  kill -s SIGQUIT "$PPID_"
  exit 130
}

trap handler SIGINT

# better do some sleeping:

for (( ;; )); do
  [[ -n $SLEEPPID ]] && kill -s 0 "$SLEEPPID" &>/dev/null || {
    sleep 20 &
    SLEEPPID=$!
  }
  wait
done

您的 script1.sh 中的原始最后一行也可能与此类似,具体取决于您的脚本预期实现。

./script2.sh || exit
...

或者

./script2.sh
[[ $? -eq 130 ]] && exit
...
于 2013-08-28T06:13:39.453 回答
0

这应该工作的正确方法是通过 setpgrp()。shell 的所有孩子都应该放在同一个 pgrp 中。当 tty 驱动程序发出 SIGINT 信号时,它将被汇总传递给所有进程。任何级别的 shell 都应该注意信号的接收,等待孩子退出然后杀死自己,再次,没有信号处理程序,有 sigint,以便他们的退出代码是正确的。

此外,当 SIGINT 被其父进程设置为在启动时忽略时,他们应该忽略 SIGINT。

作为逻辑的任何部分,shell 不应“检查孩子是否以 sigint 退出”。shell 应该始终只尊重它直接接收到的信号作为采取行动然后退出的理由。

回到真正的 UNIX 时代,SIGINT 只需按一下键即可停止 shell 和所有子进程。shell 的退出和子进程继续运行从来没有任何问题,除非它们自己将 SIGINT 设置为忽略。

对于任何 shell 管道,它们应该是从右到左的管道创建的子进程关系。最右边的命令是 shell 的直接子级,因为那是正常退出的最后一个进程。在此之前的每个命令行都是紧邻下一个管道符号或 && 或 || 右侧的进程的子进程。象征。&& 和 || 周围有明显的一群孩子 自然脱落。

最后,进程组保持干净,以便 nohup 与所有接收 SIGINT 或 SIGQUIT 或 SIGHUP 或其他 tty 驱动程序信号的子进程一样工作。

于 2020-02-07T23:14:54.937 回答