【问题标题】:How to Kill Current Command When Bash Script is Killed如何在 Bash 脚本被终止时终止当前命令
【发布时间】:2012-10-24 15:40:32
【问题描述】:

我目前有一个看起来像这样的脚本。

# code

mplayer "$vid"

# more code

问题是,如果这个脚本被杀死,mplayer 进程仍然存在。我想知道如何才能让杀死脚本也会杀死 mplayer。

我不能使用exec,因为我需要在mplayer之后运行命令。

exec mplayer "$vid"

我能想到的唯一可能的解决方案是在后台生成它并等待它手动完成。这样我就可以获得它的 PID 并在脚本被杀死时杀死它,而不是完全优雅。我想知道这样做的“正确”或最佳方式是什么。

【问题讨论】:

    标签: linux bash shell sh kill


    【解决方案1】:

    我能够测试我在评论中发布的 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

    的包装脚本

    当包装脚本因任何原因(甚至是 SIGKILL)被终止时,foo.sh 应该得到 SIGTERM。

    注意:这是一个仅限 linux 的解决方案,提供的 c 源代码没有详细检查参数

    【讨论】:

    • 这真的很棒(还没有测试,但我会假设它有效)。不过,如果有一个 bash 解决方案,那就太棒了。我想我会接受这个答案,即使我的答案是我计划使用的答案(我不太担心 SIGKILL),因为这个答案完全解决了问题。
    • 另外,您是否要重新发布或添加指向此相关问题的答案的链接 (unix.stackexchange.com/questions/54963/…),因为它比任何其他给出的答案都更好。
    • 好的,我开始尝试它,它有一些错误。我修复它并提交和编辑。旧脚本正在监听父母的死亡,但它是父母。新脚本forks 和孩子要求在execing 之前接受父母的死亡。我还删除了名称修改,因为它破坏了不在路径或当前目录中的文件。
    • 我用 exec 设置它的方式是它被设计为由 shell 脚本包装。当 shell 脚本终止时,无论通过 prun 启动的 shell 脚本都会收到信号。用 fork 做这件事很好,但它呈现出不同的语义。只有当 prun 本身死亡时才会传递信号。至于名字修饰,我犯了一个错误。我的意思是 argv0 是 args 的新 argv0,而不是执行的命令。无论如何,它对中心思想并不重要。
    • 哦,我明白了。我的脚本将是相同的,除了我将运行 prun mplayer ... 而不是 mplayer ...。好的。那效果很好。也许您应该更改答案以使用mplayer 而不是foo.sh,因为mplayer 是问题中使用的示例。这将有助于使其更加清晰。
    【解决方案2】:

    感谢 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。它会立即终止进程。如果它可以被抓住,它就没有用了。不幸的是,这个案子无法处理。
    • 实际上,我做了一些研究,(xfce-)终端有办法做到这一点。我会进一步研究(将 SIGKILL 添加到陷阱不起作用)。
    • 我似乎与会话有关。我有一个片段,当它放在脚本的顶部时,会导致 mplayer 在被杀死 pastebin.com/UUf5muW5 时死亡。不幸的是,由于某种原因,这在 xscreensaver 中不起作用,并且 xscreensaver 实际上将脚本设置为会话控制器。这可能是 xscreensaver 中的错误,或者 mplayer 可能正在做一些有趣的事情。
    【解决方案3】:

    更新)我想我明白你现在在寻找什么:

    您可以通过生成一个新终端来运行您的脚本来完成此操作:

    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,而不是等到它完成。

    【讨论】:

    • 问题是我没有运行我的脚本,它正在运行我的另一个程序。
    • @KevinCox 哪个其他程序正在运行您的脚本?您可以让程序执行gnome-terminal -x /path_to_dir_of_your_script/your_script_name 而不仅仅是您的脚本名称吗?
    • XScreensaver。我想我可以,但它肯定不会那么优雅。如果找不到更好的,我会这样做。
    • 我认为这是最好的答案。终端正在为您发送叹息,因此您不必捕获和发送自己的信号。我认为您所做的任何事情都会从本质上重新实现终端正在做的事情
    • 如果您在 linux 上,您可以尝试将当前脚本包装在另一个脚本中,然后在包装器被终止时(甚至通过 SIGKILL)将您选择的一个信号传递给您当前的脚本,您然后可以用来杀死你的进程树。请参阅:stackoverflow.com/questions/12193581/… 虽然我不知道如何从 shell 脚本调用 prctl...
    【解决方案4】:

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

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

    然后像这样使用 kill:

    kill -TERM -<gid>
    

    请注意进程组 ID 前的破折号。或单线:

    kill -TERM -$(pgrep <name>)
    

    【讨论】:

    • 问题是我没有杀死我的脚本。它被我(出于所有意图和目的)无法控制的程序运行和杀死。
    • @KevinCox 在那种情况下,除了捕获信号并杀死子进程或类似的东西之外,我没有其他方法可以做到这一点。没那么糟糕。
    【解决方案5】:

    也许使用command substitution 在子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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-10
      • 2015-04-24
      • 2022-10-14
      • 2010-12-11
      • 2016-10-01
      • 1970-01-01
      • 2016-04-29
      • 2021-05-27
      相关资源
      最近更新 更多