Ловушка Bash не убивает детей, вызывает неожиданное поведение 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] завершает работу, но оставляет запущенными pids. [ctrl-c] снова убивает bash и все остальные процессы на pts/4.

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

-- Источники кода:

Идея Innotify из: https://exyr.org/2011/inotify-run/

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


person vastlysuperiorman    schedule 20.07.2015    source источник
comment
Здесь много гадкого. args=$@, например, ошибочно на первый взгляд — $@ — это массив, args — это скаляр. Вы не можете надежно поместить массив в скаляр, и когда вы запустите $args позже, вы столкнетесь со всеми ловушками в mywiki.wooledge.org/BashFAQ/050.   -  person Charles Duffy    schedule 20.07.2015
comment
Кроме того, цитирование. Итак, так очень неправильно.   -  person Charles Duffy    schedule 20.07.2015
comment
(Кроме того, не используйте SIGKILL, если вы уже не передали что-то SIGTERM и не подождали достаточно долго, чтобы попытаться изящно очиститься).   -  person Charles Duffy    schedule 20.07.2015
comment
Если вы хотите неявно отслеживать внуков и т. д., рассмотрите возможность использования файла блокировки и fuser -k вместо того, чтобы пытаться анализировать ps.   -  person Charles Duffy    schedule 20.07.2015
comment
Кроме того, echo -e является злом - например, bash нарушает спецификацию POSIX (не расширяет, нарушает - POSIX-совместимое эхо будет печатать -e на своем выходе при передаче -e в своей командной строке), включая его; см. pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html. printf — лучшая альтернатива.   -  person Charles Duffy    schedule 20.07.2015
comment
О чувак. Рад, что опубликовал... похоже, мне есть чему здесь поучиться. @CharlesDuffy спасибо за ссылки, я почитаю.   -  person vastlysuperiorman    schedule 20.07.2015
comment
@CharlesDuffy Вы упоминаете, что цитата неверна ... можете ли вы уточнить, о какой цитате вы говорите? Ты имеешь в виду ловушку? Если да... как мне лучше написать?   -  person vastlysuperiorman    schedule 20.07.2015
comment
Что касается цитирования, попробуйте запустить исходный код через shellcheck.net.   -  person Charles Duffy    schedule 20.07.2015
comment
...или см. mywiki.wooledge.org/Quotes для более подробного ознакомления.   -  person Charles Duffy    schedule 20.07.2015
comment
...и если вы хотите узнать, как правильно использовать массивы в bash, см. BashFAQ #5: mywiki .wooledge.org/BashFAQ/005   -  person Charles Duffy    schedule 20.07.2015


Ответы (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
}
person Charles Duffy    schedule 20.07.2015
comment
Это решение также не работает. Попробуйте запустить это с помощью шагов, которые я предложил (т.е. источник файла, вызов rerun ./parent.sh). Когда вы нажимаете [ctrl-c] для выхода, процессы не уничтожаются, а повторное нажатие [ctrl-c] убивает терминал. - person vastlysuperiorman; 20.07.2015
comment
Интерактивная оболочка — это совсем другая куча червей. Это действительно тот вариант использования, для которого вы это пишете? Я создал сценарий с именем rerun, который просто вызывает rerun "$@", как точно определено выше, и делает все правильно, когда вызывается как ./rerun sh -c 'while sleep 1; do ((++i)); echo "$i"; done'. - person Charles Duffy; 20.07.2015
comment
Краткая форма: не определяйте rerun как функцию; поместите его в свой собственный сценарий. Нет причин заниматься головной болью интерактивного управления заданиями. - person Charles Duffy; 20.07.2015