Bash:当子脚本陷阱SIGINT时,为什么父脚本不会在SIGINT上终止?

时间:2013-08-28 00:59:30

标签: linux bash process

script1.sh:

 #!/bin/bash    

./script2.sh
 echo after-script

script2.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位于同一进程组中,因此在命令行上按Ctrl + C时,SIGINT将被发送到两个脚本。这就是为什么我不希望script2.sh在script2.sh退出时继续。

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

5 个答案:

答案 0 :(得分:11)

新答案:

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

What happens to a SIGINT (^C) when sent to a perl script containing children?

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

  

Perl的内置系统功能就像C系统一样(3)   就信号而言,从标准C库中起作用。   如果您使用的是Perl版本的system()或管道打开或反引号,   那么父母 - 一个呼叫系统而不是一个呼叫系统   它会在孩子们的时候忽略任何SIGINT和SIGQUIT   运行

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

  

还有一种方法是调用shell可以判断是否调用了   程序退出SIGINT并忽略SIGINT(或用于   其他目的)。就像在WUE方式中一样,shell会等待孩子   完成。它确定程序是否在SIGINT上结束,如果   所以,它停止了脚本。如果程序做了任何其他退出,那么   脚本将继续。我会称之为做事的方式   本文其余部分的“WCE”(用于“等待和合作退出”)。

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

旧回答:

Bash脚本中命令的非零退出状态不会终止程序。如果您在echo $?后执行./script2.sh,则会显示130.您可以使用set -e作为phs建议终止脚本。

$ help set
...
-e  Exit immediately if a command exits with a non-zero status.

答案 1 :(得分:2)

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

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

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

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

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

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

#!/bin/bash

function handler {
  trap INT
  kill -2 $$
}

trap handler INT
badprocess "$@"

答案 2 :(得分:0)

原因是script1.sh未终止,script2.sh正在子shell中运行。要使前一个脚本退出,您可以按照phs和seanmcl的建议设置-e,或强制script2.sh在同一个shell中运行,然后说:

. ./script2.sh

在你的第一个脚本中。如果您在执行脚本之前执行set -x,那么您所观察到的内容将会很明显。 help set告诉:

  -x  Print commands and their arguments as they are executed.

答案 3 :(得分:0)

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

script1.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

script2.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

您脚本中的原始最后一行也可能就像这样,具体取决于您的脚本预期实现。

./script2.sh || exit
...

或者

./script2.sh
[[ $? -eq 130 ]] && exit
...

答案 4 :(得分:0)

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

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

作为逻辑的任何部分,外壳程序都不应该“检查子代是否退出并带有sigint”。外壳程序应该始终尊重直接接收到的信号,作为采取行动然后退出的理由。

在真正的UNIX时代,SIGINT只需一次击键就停止了Shell和所有子进程。退出外壳程序和继续运行子进程从来没有任何问题,除非它们本身已将SIGINT设置为忽略。

对于任何shell管道,它们应该是从右到左的管道创建的子进程关系。最正确的命令是外壳程序的直接子级,因为多数民众赞成在最后一个退出正常进程。在此之前的每个命令行是紧接在下一个管道符号或&&或||右侧的过程的子级。符号。 &&和||周围有明显的孩子群体。会自然脱落。

最后,进程组保持工作整洁,以便nohup正常工作,并且所有接收SIGINT或SIGQUIT或SIGHUP或其他tty驱动程序信号的子代也一样。