【问题标题】:Identify whether a process was killed by a signal in bash识别进程是否被 bash 中的信号杀死
【发布时间】:2018-11-01 01:07:44
【问题描述】:

考虑这两个 C 程序:

#include <signal.h>

int main(void) {
    raise(SIGTERM);
}
int main(void) {
    return 143;
}

如果我运行其中任何一个,bash 中 $? 的值将是 143。不过,The wait syscall 可以让您区分它们:

wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGTERM}], 0, NULL) = 11148
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 143}], 0, NULL) = 11214

而且 bash 清楚地使用了这些知识,因为第一个导致 Terminated 被打印到终端(奇怪的是,即使我将 stdout 和 stderr 都重定向到其他地方也会发生这种情况),而第二个则没有。如何将这两种情况与 bash 脚本区分开来?

【问题讨论】:

  • 我认为在shell中没有任何方法可以区分它们。
  • 按照惯例,程序不会以高于 126 的状态退出,因此您可以简单地检查 $? -gt 126 是否可以正常退出或从信号中退出。
  • 我认为小助手(Perl/Python)可以帮助 bash 脚本。这行得通,还是必须是纯 bash ?
  • 没有外部工具是不可能的

标签: bash signals exit-code


【解决方案1】:

我相信从纯 bash/shell 获取完整的退出代码是不可能的。 Unix' StackExchange上的答案很全面。

所有 shell 之间的共同点是,如果进程正常终止,$? 包含退出代码的最低 8 位(传递给 exit() 的数字)。

不同之处在于进程被信号终止的时间。在所有情况下,这是 POSIX 要求的,该数字将大于 128。POSIX 没有指定该值可能是什么。但实际上,在我所知道的所有类似 Bourne 的 shell 中,$? 的最低 7 位将包含信号编号。但是,n 是信号编号,

  • 在 ash、zsh、pdksh、bash、Bourne shell 中,$?128 + n。这意味着在那些 shell 中,如果你得到一个 $?129,你不知道是因为进程以 exit(129) 退出还是被信号 1 (@987654334 @ 在大多数系统上)。但基本原理是,当 shell 退出时,默认情况下会返回最后一个退出命令的退出状态。通过确保 $? 永远不会大于 255,这允许具有一致的退出状态:

    $ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    bash: line 1: 16720 Terminated              sh -c "kill \$\$"
    8f # 128 + 15
    $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    bash: line 1: 16726 Terminated              sh -c "kill \$\$"
    8f # here that 0x8f is from a exit(143) done by bash. Though it's
       # not from a killed process, that does tell us that probably
       # something was killed by a SIGTERM
    

出于这个原因,我相信您需要在 bash 之外运行命令来捕获退出代码。


通过一些抽象,similar question 被问及unbuffer,这是一个用 tcl 编写的小脚本。更准确地说,unbuffer 使用带有 tcl/tk 包装器的库 libexpect。 我从 unbuffer 的源代码中提取了相关代码以得出解决方法:

#!/bin/bash

expectStat() {
expect <(cat << EOT
set stty_init "-opost"
set timeout -1
eval [list spawn -noecho ] $@
expect
send_user "[wait]\n"
EOT
)
}

expectStat sleep 5 & 
wait

如果sleep 正常退出,则返回大约以下行:

18383 exp4 0 0

如果 sleep 在它自己退出之前被杀死,上面的脚本将大致返回:

18383 exp4 0 0 CHILDKILLED SIGTERM {software termination signal}

如果脚本以exit 143 终止,则该脚本将大致返回:

18383 exp4 0 143

这些字符串的含义可以从expect的手册中提取。集成函数wait 正在返回上述返回行。 前两个值是 pid 和 expect 的进程名称。 四是退出状态。如果出现信号,则会打印更多信息。第六个值是在进程终止时发送给进程的信号。

等待

通常返回四个整数的列表。第一个整数是等待的进程的 pid。第二个整数是对应的 spawn id。如果发生操作系统错误,则第三个整数为 -1,否则为 0。如果第三个整数为 0,则第四个整数是衍生进程返回的状态。如果第三个整数是-1,第四个整数就是操作系统设置的errno值。还设置了全局变量errorCode。

其他元素可能会出现在等待返回值的末尾。可选的第五个元素标识一类信息。目前,该元素唯一可能的值是 CHILDKILLED,在这种情况下,接下来的两个值是 C 风格的信号名称和简短的文本描述。

这意味着第四个值,如果存在,第六个值就是您要查找的值。存储整行并提取信号和退出代码,例如使用以下代码:

RET=$(expectStat script.sh 1>&1)

# Filter status
EXITVALUE="$(echo "$RET" | cut -d' ' -f4)"
SIGNAL=$(echo "$RET" | cut -d' ' -f6)

#echo "Exit value: $EXITVALUE, Signal: $SIGNAL" 

if [ -n "$SIGNAL" ]; then
        echo "Likely killed by signal"
else
        echo "$EXITVALUE"
fi

最后,这种解决方法非常不雅。也许,还有另一种工具,它自带了基于 c 的工具来获取信号的出现。

【讨论】:

    【解决方案2】:

    wait 是一个系统调用,也是一个bash builtin

    为了将这两种情况与 bash 区分开来,在后台运行程序并使用内置的wait 报告结果。

    以下是非零退出代码和未捕获信号的示例。这些示例在子 bash shell 中使用 exitkill bash 内置函数,而不是运行程序的子 bash shell。

    $ bash -c 'kill -s SIGTERM $$' & wait
    [1] 36068
    [1]+  Terminated: 15          bash -c 'kill -s SIGTERM $$'
    $ bash -c 'exit 143' & wait
    [1] 36079
    [1]+  Exit 143                bash -c 'exit 143'
    $
    

    至于为什么即使重定向 stdout 和 stderr 也会看到Terminated 打印到终端,原因是它是由 bash 打印的,而不是由程序打印的。

    更新:

    通过显式使用 wait 内置函数,您现在可以将其 stderr(带有程序的退出状态)重定向到单独的文件。

    以下示例显示了三种终止类型:正常退出 0、非零退出和未捕获信号。 wait报告的结果存储在带有相应程序PID标记的文件中。

    $ bash -c 'exit 0' & wait 2> exit_status_pid_$!
    [1] 40279
    $ bash -c 'exit 143' & wait 2> exit_status_pid_$!
    [1] 40291
    $ bash -c 'kill -s SIGTERM $$' & wait 2> exit_status_pid_$!
    [1] 40303
    $  for f in exit_status_pid*; do echo $f: $(cat $f); done
    exit_status_pid_40279: [1]+ Done bash -c 'exit 0'
    exit_status_pid_40291: [1]+ Exit 143 bash -c 'exit 143'
    exit_status_pid_40303: [1]+ Terminated: 15 bash -c 'kill -s SIGTERM $$'
    $
    

    【讨论】:

    • 这很优雅!感谢分享
    【解决方案3】:

    这与 bash 相差甚远,但 bcc 提供 exitsnoop。使用 描述中的示例,关于 Debian Sid:

    root@vsid:~# apt install bpfcc-tools linux-headers-amd64
    root@vsid:~# exitsnoop-bpfcc
    PCOMM            PID    PPID   TID    AGE(s)  EXIT_CODE
    example1         1041   948    1041   0.00    signal 15 (TERM)
    example2         1042   948    1042   0.00    code 143
    ^C
    

    请参阅install guide 了解其他发行版。

    【讨论】:

      【解决方案4】:

      Strace 可以捕获大部分信号,但可能不适用于系统调用(例如 kill -9 ),因此,如 this article 所述:

      Auditd 是一个守护进程或服务,顾名思义,它会生成系统级活动的审核日志。它作为审计包从通常的存储库安装,然后在 /etc/audit/auditd.conf 中进行配置,规则在 /etc/audit/audit.rules 中。

      本文提供了审计输出的示例,可以帮助确定它是否对您有帮助:

      通常的输出如下所示:

      时间->2015 年 6 月 3 日星期三 16:34:08 type=SYSCALL msg=audit(1433363648.091:6342): arch=c000003e syscall=62 成功=no exit=-3 a0=1e06 a1=0 a2=1e06 a3=ffffffffffffffff0 items=0 ppid=10044 pid=10140 auid=500 uid =0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=2 comm=4174746163682041504920696E6974 exe="/opt/ibm/WebSphere/AppServer/java/jre/bin/ java" subj=unconfined_u:unconfined_r:unconfined_java_t:s0-s0:c0.c1023 key="kill_signals"

      还提到了 System Tap,并在指南中添加了 redirection

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-11
        • 2011-03-28
        • 1970-01-01
        • 1970-01-01
        • 2010-12-08
        • 2021-09-10
        相关资源
        最近更新 更多