Ловушка Баш не убивает детей, вызывает неожиданное поведение ctrl-c

редактировать

Для будущих читателей. Корень этой проблемы на самом деле сводится к запуску функции в интерактивной оболочке, а не к отдельному сценарию.

Кроме того, есть много вещей, которые можно улучшить в коде, который я первоначально разместил. Пожалуйста, смотрите комментарии для вещей, которые могли / должны были быть сделаны лучше.

/редактировать

У меня есть функция 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] выходит, но запускает пиды. [ctrl-c] снова убивает bash и все остальные процессы в pts/4.

Желаемое поведение: на [ctrl-c] убить детей и нормально выйти из оболочки. Помогите?

- Исходники кода:

Inotify идея от: https://exyr.org/2011/inotify-run/

Убить детей из: http://riccomini.name/posts/linux/2012-09-25-kill-subprocesses-linux-bash/

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
}
Другие вопросы по тегам