我有一个启动 3 个终端模拟器窗口的脚本:
#!/bin/sh
terminator --role='terminator-left' 2>/dev/null &
sleep 0.1
terminator --role='terminator-center' 2>/dev/null &
sleep 0.1
terminator --role='terminator-right' 2>/dev/null &
(我使用 --role 选项,以便我可以自动将窗口放置在不同的显示器上,在我的 openbox 配置文件中定义)
我在不同的虚拟桌面上多次启动此脚本。
如何在某种“进程组”中启动这 3 个终端,以便在其中任何一个进程终止时可以杀死所有剩余进程?
重要提示:我不想终止可能在脚本的不同实例中启动的所有终端(我不能只使用 pkill 或 pgrep 来终止与模式匹配的任何进程)
换句话说,假设我在虚拟桌面 1 和虚拟桌面 2 上启动脚本。
我现在在 2 个不同的虚拟桌面上有 3 个终端窗口。
当我退出虚拟桌面 1 上的一个终端时,我希望剩余 2 个窗口自动终止,但不会影响在虚拟桌面 2 上运行的其他 3 个窗口的独立实例。
我所描述的可能吗?
我怎样才能做到这一点?
答案1
免责声明:
我没有机会在 openbox 下测试我的建议。我仅在 kde-plasma 下验证了它,
我的解决方案依赖于wait
shell 内部命令如 POSIX 程序员手册中所述。使用 -n 选项,某些特定的实现可以使脚本更简单。
#!/bin/sh
ownkill()
{
trap - CHLD
pkill --signal 15 -P $$
}
terminator --role='terminator-left' 2>/dev/null &
sleep 0.1
terminator --role='terminator-center' 2>/dev/null &
sleep 0.1
terminator --role='terminator-right' 2>/dev/null &
trap ownkill CHLD
wait
该脚本基于以下想法:
- 启动 3终结者根据需要进行处理,
- 等待它们完成,
- 以终止其余进程的方式处理终止上述进程之一的 SIGCHLD 信号。
注1(重置处理程序): 正如 aviro 在注释中正确观察到的那样,信号处理程序必须将 SIGCHLD 的处理重置为默认值,因为杀死剩余进程将触发其他(最多 2 个)SIGCHLD。
如果不这样做,将迫使 shell 处于某种递归情况(处理程序必须处理它自己生成的信号),根据 shell 实现或多或少地进行优雅的管理。 (从无害的警告到某些超出最大递归深度或同等。错误。)
注 2(保持 SIGCHLD 默认处理直到等待):正如 Martin Vegter 在评论中正确观察到的那样,该trap ownkill CHLD
指令应紧接在等待指令之前放置。
将其作为脚本的第一条指令(正如我在前面的版本中荒谬地建议的那样)将在第一个sleep
进程终止后立即触发专用处理程序。
答案2
使用 bash,您可以简单地收集所有进程 id,并在最后等待,以便当任何进程退出(bash 的wait -n
)时,所有 id 都会被一些合适的信号杀死:
#!/bin/bash
terminator --role='terminator-left' 2>/dev/null &
pid1=$!
sleep 0.1
terminator --role='terminator-center' 2>/dev/null &
pid2=$!
sleep 0.1
terminator --role='terminator-right' 2>/dev/null &
pid3=$!
wait -n
kill -hup $pid1 $pid2 $pid3 2>/dev/null
由于其中一个 pid 已经失效,我们将kill
stderr 重定向到 null 以避免出现错误消息。
答案3
您可能愿意使用 systemd 瞬态单元,因为它的启动、控制和停止都很简单。假设您的 shell 脚本是,在其末尾./myscript
添加一个wait -n
命令,以便它不会立即完成,那么您可以使用以下命令启动它:
systemd-run --user --unit=mytest1 ./myscript
此命令立即返回消息Running as unit: mytest1.service
,您可以使用常用命令查看创建的瞬态单元中正在运行哪些进程:
systemctl status --user mytest1
您可以使用一个命令停止所有进程:
systemctl stop --user mytest1
要多次运行同一脚本,只需确保
--unit=
每次使用不同的名称即可。如果它们都以 开头test
,那么您可以检查它们全部,或者通过将它们引用为 来停止它们test*
。
看man systemd 运行了解可能有用的各种选项,例如
--setenv=
、--working-directory=
、--collect
、--wait
。请注意,您还可以在命令末尾将参数传递给脚本systemd-run
。