我有一个忽略 SIGINT 的程序,但我想在前台运行。我想找到一种方法来强制它在 Ctrl-C 上关闭。有没有什么方法可以编写一个包装器(您可以调用它./wrapper.sh my_program
)来强制行为不当的程序退出,可能是通过检测被忽略的 SIGINT 并生成 SIGKILL ?
这个答案与我正在寻找的完全相反——我想强制一个忽略信号的程序在 SIGINT 上退出。
答案1
让我们构建一个将 SIGINT“转换”为 SIGKILL 的包装器。
我们需要一个运行并在+my_program
上获取 SIGINT的进程。当它收到 SIGINT 时,它应该发送 SIGKILL 到。请注意,我们不需要实际接收由+引起的 SIGINT ;由于额外的进程而导致 SIGKILL 就足够了。Ctrlcmy_program
my_program
Ctrlc
相关事实:
- 沿着另一个进程运行一个进程的标准方法是在后台异步运行其中一个进程(即终止
&
)。 - 当您点击Ctrl+时c,终端仿真器将 SIGINT 发送到前台进程组中的进程。
- 一些(简单的)shell 在同一进程组中运行所有内容。如果他们被告知在后台运行命令,那么他们会将其标准输入重定向到
/dev/null
或等效文件,以防止该命令窃取输入。 - 其他 shell 可以在单独的进程组中运行每个命令。如果他们被告知在后台运行命令,那么他们将保持其标准输入不变;后台的命令仍然无法窃取控制终端的输入,因为
SIGTTIN
。这允许 shell 通过通知终端新的前台进程组来将作业从后台移动到前台。该机制称为作业控制,可以禁用。在脚本中它默认是禁用的。 - 无论哪种方式,后台进程都无法从终端读取数据。这意味着我们不应该
my_program
在后台运行,以防 stdin 是终端并且my_program
需要从中读取。 - 不幸的是,在某些 shell 中,我们也不应该在后台运行其他进程。即使禁用作业控制,某些 shell 也会使用单独的进程组。不在前台进程组中的其他进程将不会收到Ctrl+上的 SIGINT c,因此它无法将其“转换”为 SIGKILL。
- 在我的 Debian 10 中,
posh
有一个 shell 在同一进程组中运行所有内容。
这导致以下包装器:
#!/usr/bin/env posh
( trap 'kill -s KILL 0' INT
while kill -s 0 "$$" 2>/dev/null; do sleep 1; done
) &
exec "$@"
exec "$@"
运行my_program
(或您指定的任何内容),可能带有参数。感谢exec
my_program
将更换包装纸。它不仅能够从标准输入(可能是终端)读取数据;它的 PID 将是被替换的包装器之一。从这个意义上说,包装纸是透明的。此外,它还有一个很好的功能:PID在启动my_program
之前就已知my_program
,我们可以轻松地在其他进程(在本例中是在后台的子 shell 中)中使用它来检测my_program
实际终止的时间。
将trap
SIGINT“转换”为 SIGKILL。注意kill -s KILL 0
将 SIGKILL 发送到整个进程组,包括子进程(my_program
如果它们在该组中)(如果您只想杀死my_program
,则kill -s KILL "$$"
改为使用)。尽管如此,仅kill -s 0 "$$"
测试其存在性my_program
。
有一种替代方案不需要 shell 来运行同一进程组中的所有内容。诀窍是:管道中的进程应该在一个进程组中运行;通过巧妙的重定向,您可以构建一条管道,其中各部分之间不相互连接。
#!/bin/sh -
exec 9>&1
( "$@"; kill -s TERM 0 ) >&9 9>&- | (
trap 'kill -s KILL 0' INT
while :; do sleep 1; done
)
在此变体中my_program
不会替换包装器。第二个kill
是将SIGINT“转换”为SIGKILL。第一个kill
是终止循环,以防my_program
在循环本来可以生存的情况下退出。
有一些场景可能无法按您的预期工作(使用任何包装器)。他们之中:
- 如果
my_program
分叉并退出,则将真正的工作留给子进程。所讨论的麻烦程序很可能不会像这样运行,因为您尝试Ctrl+c它。但一般来说可能是这样。 - 如果
my_program
在另一个进程组中生成子进程,并且您希望将它们与其父进程一起杀死。 - 如果
my_program
配置终端不在Ctrl+上发送 SIGINT c。如果您怀疑发生这种情况,请通过在和stty -F /dev/tty intr ^C
之间放置来改进包装器。在这种情况下,程序甚至可能不会忽略 SIGINT,它可能只是确保它不会从终端获取它。所以也许 SIGKILL 有点矫枉过正;也许恢复终端的此功能就足够了,+将开始工作。sleep 1
done
Ctrlc
来自评论:
忽略 SIGINT 的程序通常有一个很好的理由。
真的。尝试使用 SIGTERM 或 SIGHUP 而不是 SIGKILL。也许有问题的程序不会忽略至少其中之一,并通过适当的清理优雅地退出。