在脚本终止时终止后台进程的可靠技术是什么?

sqw*_*eek 7 linux bash shell process

我使用 shell 脚本对系统事件做出反应并更新窗口管理器中的状态显示。例如,一个脚本通过侦听多个来源来确定当前的 wifi 状态:

  1. 从 wpa_supplicant 关联/分离事件
  2. 地址从 ip 更改(所以我知道 dhcpcd 何时分配了地址)
  3. 一个计时器进程(所以信号强度会不时更新)

为了实现多路复用,我最终生成了后台进程:

{ wpa_cli -p /var/run/wpa_supplicant -i wlan0 -a echo &
ip monitor address &
while sleep 30; do echo; done } |
while read line; do update_wifi_status; done &
Run Code Online (Sandbox Code Playgroud)

即,设置是每当任何事件源输出一行时,我的 wifi 状态都会更新。整个管道在后台运行(最后一个“&”),因为我还观察了另一个导致脚本终止的事件源:

wait_for_termination
kill $!
Run Code Online (Sandbox Code Playgroud)

kill 应该清理后台进程,但在这种形式中它并不能完全完成这项工作。'wpa_cli' 和 'ip' 进程总是存活,至少,它们也不会在下一个事件中死亡(理论上它们应该得到一个 SIGPIPE;我猜读取进程也必须仍然存在)。

问题是,如何可靠地[并且优雅地!]清理所有产生的后台进程?

sqw*_*eek 10

超级简单的解决方案是在脚本末尾添加:

kill -- -$$

解释:

$$给了我们正在运行的 shell 的 PID。所以,kill $$会向 shell 进程发送一个 SIGTERM。但是,如果我们否定PID ,则会kill进程组中的每个进程发送一个 SIGTERM 。我们需要--事先kill知道这-$$是一个进程组 ID 而不是一个标志。

请注意,这依赖于正在运行的 shell 作为进程组的领导者!否则,$$(PID)将与进程组 ID 不匹配,并且您最终会向谁知道在哪里发送信号(好吧,可能没有任何地方,因为如果我们不是一个组,则不太可能有一个具有匹配 ID 的进程组领导者)。

当 shell 启动时,它会创建一个新的进程组 [1]。每个分叉的进程都成为该进程组的成员,除非它们通过系统调用 ( setpgid)显式更改其进程组。

保证特定脚本作为进程组领导运行的最简单方法是使用setsid. 例如,我有一些从父脚本启动的状态脚本:

#!/bin/sh
wifi_status &
bat_status &
Run Code Online (Sandbox Code Playgroud)

像这样写,wifi和battery脚本都和父脚本在同一个进程组运行,kill -- -$$不工作。修复方法是:

#!/bin/sh
setsid wifi_status &
setsid bat_status &
Run Code Online (Sandbox Code Playgroud)

我发现pstree -p -g可视化进程和进程组 ID 很有用。

感谢所有贡献并让我深入挖掘的人,我学到了东西!:)

[1] shell 是否还有其他情况会创建进程组?例如。启动子shell?我不知道...