【问题标题】:Bash trap not killing children, causes unexpected ctrl-c behaviorBash 陷阱不会杀死孩子,导致意外的 ctrl-c 行为
【发布时间】:2015-07-20 15:58:09
【问题描述】:

编辑

为未来的读者。这个问题的根源实际上归结为在交互式 shell 中运行函数而不是将其放在单独的脚本中。

此外,我最初发布的代码中还有很多可以改进的地方。请参阅 cmets 了解可以/应该做得更好的事情。

/编辑

我有一个 bash 函数,用于在目录中的文件更改时在后台重新运行进程(类似于 Grunt,但用于一般目的)。脚本在运行时按需要运行:

  • 子进程已正确启动(包括任何子进程)
  • 在文件更改时,子被杀死(包括孩子)并重新启动

但是,在退出 (ctrl-c) 时,没有任何进程被杀死。此外,再次按 ctrl-c 将终止当前终端会话。我假设这是我的陷阱的问题,但无法确定问题的原因。

这里是rerun.sh的代码

#!/bin/bash
# rerun.sh

_kill_children() {
    isTop=$1
    curPid=$2
        # Get pids of children
    children=`ps -o pid --no-headers --ppid ${curPid}`
    for child in $children
    do
            # Call this function to get grandchildren as well
            _kill_children 0 $child
    done
    # Parent calls this with 1, all other with 0 so only children are killed
    if [[ $isTop -eq 0 ]]; then
            kill -9 $curPid 2> /dev/null
    fi
}

rerun() {
    trap " _kill_children 1 $$; exit 0" SIGINT SIGTERM
    FORMAT=$(echo -e "\033[1;33m%w%f\033[0m written")
    #Command that should be repeatedly run is passed as args
    args=$@
    $args &

    #When a file changes in the directory, rerun the process
    while inotifywait -qre close_write --format "$FORMAT" .
    do
        #Kill current bg proc and it's children
        _kill_children 1 $$
        $args & #Rerun the proc
    done
}

#This is sourced in my bash profile so I can run it any time

要对此进行测试,请创建一对可执行文件 parent.sh 和 child.sh,如下所示:

#!/bin/bash
#parent.sh
./child.sh

#!/bin/bash
#child.sh
sleep 86400

然后获取 rerun.sh 文件并运行 rerun ./parent.sh。在另一个终端窗口中,我 watch "ps -ef | grep pts/4" 查看重新运行的所有进程(在此示例中为 pts/4)。触摸目录中的文件会触发 parent.sh 和子项的重新启动。 [ctrl-c] 退出,但让 pid 保持运行。 [ctrl-c] 再次杀死 bash 和 pts/4 上的所有其他进程。

期望的行为:在 [ctrl-c] 上,杀死孩子并正常退出到 shell。帮忙?

-- 代码来源:

Inotify 想法来自:https://exyr.org/2011/inotify-run/

杀死来自:http://riccomini.name/posts/linux/2012-09-25-kill-subprocesses-linux-bash/的孩子

【问题讨论】:

  • 这里有很多讨厌的东西。例如,args=$@ 表面上是错误的——$@ 是一个数组,args 是一个标量。您不能可靠地将数组放入标量中,当您稍后运行$args 时,您会遇到mywiki.wooledge.org/BashFAQ/050 中的所有陷阱。
  • 还有,引用。所以,so 大错特错。
  • (另外,不要使用 SIGKILL,除非你已经给了某个 SIGTERM 并等待足够长的时间让它尝试优雅地清理)。
  • 如果您想隐式跟踪孙子等,请考虑使用锁定文件和fuser -k,而不是尝试解析ps
  • 另外,echo -e 是邪恶的——例如,bash 违反了 POSIX 规范(不是扩展,违反——当通过 -e 时,符合 POSIX 的回显将在其输出上打印 -e在其命令行上)通过包含它;见pubs.opengroup.org/onlinepubs/009604599/utilities/echo.htmlprintf 是最佳实践替代方案。

标签: linux bash inotify


【解决方案1】:

首先,这不是一个好习惯。明确跟踪您的孩子:

children=( )
foo & children+=( "$!" )

...然后,您可以明确地杀死或等待它们,请参阅"${children[@]}" 的列表。如果您也想获得孙子,这是fuser -k 和锁定文件的好用户:

lockfile_name="$(mktemp /tmp/lockfile.XXXXXX)" # change appropriately
trap 'rm -f "$lockfile_name"' 0

exec 3>"$lockfile_name" # open lockfile on FD 3
kill_children() {
    # close our own handle on the lockfile
    exec 3>&-

    # kill everything that still has it open (our children and their children)
    fuser -k "$lockfile_name" >/dev/null

    # ...then open it again.
    exec 3>"$lockfile_name"
}

rerun() {
    trap 'kill_children; exit 0' SIGINT SIGTERM
    printf -v format '%b' "\033[1;33m%w%f\033[0m written"

    "$@" &

    #When a file changes in the directory, rerun the process
    while inotifywait -qre close_write --format "$format" .; do
        kill_children
        "$@" &
    done
}

【讨论】:

  • 这个解决方案也不起作用。尝试按照我建议的步骤运行它(即获取文件,调用rerun ./parent.sh)。当您按 [ctrl-c] 退出时,进程不会被终止,并且再次按 [ctrl-c] 会终止终端。
  • 交互式外壳是一堆完全不同的蠕虫。这真的是你写这个的用例吗?我创建了一个名为rerun 的脚本,它只调用rerun "$@",正如上面精确定义的那样,当以./rerun sh -c 'while sleep 1; do ((++i)); echo "$i"; done' 调用时它会做正确的事情。
  • 简写形式:不要将rerun定义为函数;把它放在自己的脚本中。没有理由处理令人头疼的交互式作业控制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 1970-01-01
  • 2021-02-23
  • 2016-01-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多