Bash陷阱没有杀死孩子,导致意外的ctrl-c行为

时间:2015-07-20 15:58:10

标签: linux bash inotify

修改

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

此外,我最初发布的代码中有许多内容可以改进。有关可能/应该做得更好的事情,请参阅评论。

/修改

我有一个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。在另一个终端窗口I watch "ps -ef | grep pts/4"中查看重新运行的所有进程(在本例中为pts / 4)。触摸目录中的文件会触发parent.sh和children的重启。 [ctrl-c]退出,但让pids继续运行。 [ctrl-c]再次杀死bash和pts / 4上的所有其他进程。

所需行为:在[ctrl-c]上,杀死孩子并正常退出到shell。帮助

- 代码来源:

Inotify idea from:https://exyr.org/2011/inotify-run/

杀死儿童:http://riccomini.name/posts/linux/2012-09-25-kill-subprocesses-linux-bash/

1 个答案:

答案 0 :(得分:3)

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

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
}