当Bash脚本被终止时如何中止当前命令

4
我现在有一个看起来像这样的脚本。
# code

mplayer "$vid"

# more code

问题是,如果这个脚本被杀死了,mplayer进程仍然存在。我想知道如何使得杀死脚本也能同时杀死mplayer。
我不能使用exec,因为我需要在mplayer之后运行命令。
exec mplayer "$vid"

我所能想到的唯一解决办法是在后台中生成它,并手动等待其完成。这样,我可以获得它的PID,并在脚本被停止时杀死它,但并不是最优雅的方法。我想知道实现这个的“正确”或最佳方式是什么。

5个回答

4
我测试了我在评论中发布的关于prctl的想法,它似乎有效。你需要编译这个代码:
#include "sys/prctl.h" #include "stdlib.h" #include "string.h" #include "unistd.h"
int main(int argc, char ** argv){ prctl(PR_SET_PDEATHSIG, atoi(argv[1]),0,0,0); char * argv0 = strdup(argv[2]); char * slashptr = strrchr(argv0, '/'); if(slashptr){ argv0 = slashptr + 1; } return execvp(argv0, &(argv[2])); }
假设你已经将上面的代码编译成了一个名为"prun"的可执行文件,并且它在你的路径中。再假设你的脚本叫做"foo.sh",也在你的路径中。请创建一个包装脚本来调用prun 15 foo.sh
当任何原因终止包装脚本时,foo.sh应该收到SIGTERM信号。
注意:这是一个仅适用于Linux的解决方案,所提供的C源代码没有详细检查参数。

这真的很棒(虽然我还没有测试,但我会假设它有效)。不过,如果有一个Bash解决方案就太棒了。我认为我会接受这个答案,尽管我打算使用我的答案(我不太担心SIGKILL),因为这个答案完全解决了问题。 - Kevin Cox
另外,您是否想要在相关问题(http://unix.stackexchange.com/questions/54963/how-can-terminal-emulators-kill-their-children-after-recieving-a-sigkill)上重新发布或添加此答案的链接,因为它比其他给出的答案更好。 - Kevin Cox
好的,我试了一下,发现有些错误。我修复了它并提交了一个编辑。旧脚本正在监听父进程的死亡,但实际上是父进程在监听。新脚本使用fork创建子进程,并在exec之前要求子进程接收父进程的死亡信号。我还删除了名称混淆,因为它对于不在路径或当前目录中的文件会出错。 - Kevin Cox
我用exec设置的方式是为了使它可以被一个shell脚本包装。当shell脚本终止时,prun通过启动的任何内容都将接收到信号。用fork做这个也没问题,但它呈现了不同的语义。当prun本身终止时,信号会被传递。至于名称混淆,我犯了一个错误。我的意思是argv0应该是args的新argv0,而不是执行的命令。无论如何,这对于中心思想并不关键。 - frankc
哦,我明白了。我的脚本将保持不变,只需运行prun mplayer ...而不是mplayer ...。好的,那就很好了。也许你应该把答案改成使用mplayer而不是foo.sh,因为mplayer是问题中使用的示例。这将有助于使它更清晰。 - Kevin Cox
仅供参考,这里是我的替代版本,它作为一个包装器,在被调用的脚本死亡时(无论如何)给出指定的信号。用法:killcatch 15 myscript.sh - Kevin Cox

3

感谢Mux的帮助。看起来在bash中没有自动捕获信号的方法。这是最终可行的(过度注释)版本。

trap : SIGTERM SIGINT # Trap these two (killing) signals.  These will cause wait
                      # to return a value greater than 128 immediately after received.

mplayer "$vid" & # Start in background (PID gets put in `$!`)
pid=$!

wait $pid # Wait for mplayer to finish.
[ $? -gt 128 ] && { kill $pid ; exit 128; } ; # If a signal was recieved
                                              # kill mplayer and exit.

参考文献: - 陷阱:http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html


你无法捕获信号9。它会立即终止进程。如果可以捕获它,那么它就没有用处了。不幸的是,这种情况无法处理。 - Kevin Cox
实际上,我做了一些研究,(xfce-) 终端有一种方法可以做到这一点。我会进一步调查它(将 SIGKILL 添加到 trap 中不起作用)。 - Kevin Cox
似乎与会话有关。我有一个片段,当放置在脚本顶部时,当其被杀死时导致mplayer死亡http://pastebin.com/UUf5muW5。不幸的是,由于某种原因,这在xscreensaver中不起作用,并且xscreensaver实际上将脚本设置为会话控制器。这可能是xscreensaver中的错误或mplayer可能正在做一些有趣的事情。 - Kevin Cox

2

(已更新) 我现在明白你想要什么了:

你可以通过生成一个新的终端来运行你的脚本来实现这一点:

gnome-terminal -x /path_to_dir_of_your_script/your_script_name

(或者使用xterm -ekonsole -e代替gnome-terminal -x,具体取决于您所在的系统)
因此,现在每当您的脚本结束/退出(我假设您在脚本的某些部分中有exit 0exit 1),新生成的终端也将退出,因为脚本已经完成-这反过来也会杀死在新终端下启动的任何应用程序。
例如,我刚刚使用此脚本测试了上述命令:
#!/bin/bash

gedit &
pid=$!
echo "$pid"

sleep 5
exit 0

如您所见,没有显式调用来杀死新的 gedit 进程,但是应用程序 (gedit) 会在脚本退出时自动关闭。

(之前的回答:或者,如果您只是想知道如何杀死进程) 这里有一个简短的示例,展示了如何使用 kill 完成这个任务。

#!/bin/bash

gedit &
pid=$!
echo "$pid"

sleep 5
kill -s SIGKILL $pid

除非我误解了您的问题,否则您可以立即获取所生成进程的PID,而不必等到它完成。

问题是我没有运行我的脚本,它是被另一个程序运行的。 - Kevin Cox
2
@KevinCox,你的脚本还运行了哪些程序?你可以让程序执行gnome-terminal -x /path_to_dir_of_your_script/your_script_name而不仅仅是脚本名称吗? - sampson-chen
XScreensaver。我猜我可以这样做,但肯定不会那么优雅。如果找不到更好的方法,我会这样做。 - Kevin Cox
1
我认为这是最好的答案。终端正在为您提供sighup,因此您不必捕获和传递自己的信号。我认为您所做的任何事情本质上都是重新实现终端正在执行的操作。 - frankc
1
如果您正在使用Linux,可以尝试将当前脚本包装在另一个脚本中,然后在包装器被终止时向当前脚本发送一个信号(即使是SIGKILL),您可以使用该信号来杀死进程树。请参见:https://dev59.com/qWct5IYBdhLWcg3wSrp3,但我不知道如何从shell脚本中调用prctl... - frankc
显示剩余2条评论

2

你可以简单地杀死进程组,这样整个进程树都会被杀死,首先找出进程组ID。

ps x -o  "%p %r  %c" | grep <name>

然后像这样使用kill命令:
kill -TERM -<gid>

请注意进程组id前面的短横线。或者说成一句话:
kill -TERM -$(pgrep <name>)

问题在于我没有杀死我的脚本。它被一个我(基本上)无法控制的程序运行和终止。 - Kevin Cox
1
在这种情况下,除了捕获信号并杀死子进程或类似的操作,我看不到其他解决方法。这并不太糟糕。 - iabdalkader

0

或许可以使用命令替换在子shell中运行mplayer "$vid"

$(mplayer "$vid")

我是这样测试的:

tesh.sh:

#!/bin/sh
$vid = "..."
$(mplayer "$vid")

% test.sh

在一个单独的终端中:
% pkill test.sh

在原始终端中,mplayer停止运行,并将错误信息打印到stderr。
Terminated
MPlayer interrupted by signal 13 in module: av_sync

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接