Як я можу вбити і чекати, коли фонові процеси завершаться в сценарії оболонки, коли я Ctrl + C це?


24

Я намагаюся налаштувати скрипт оболонки, щоб він запускав фонові процеси, а коли я Ctrlcсценарій оболонки, він вбиває дітей, а потім виходить.

Найкраще, що мені вдалося придумати, це це. Здається, що kill 0 -INTсценарій також вбиває сценарій перед тим, як чекати, тому сценарій оболонки гине до завершення дітей.

Будь-які ідеї, як змусити цей скрипт оболонки чекати, коли діти помруть після надсилання INT?

#!/bin/bash
trap 'killall' INT

killall() {
    echo "**** Shutting down... ****"
    kill 0 -INT
    wait # Why doesn't this wait??
    echo DONE
}

process1 &
process2 &
process3 &

cat # wait forever


Я знайшов це питання, шукаючи "перемикач оболонки переривання оболонки", дякую за те, що поділився командою пастки, дуже корисно виконати команду після виходу з програми із Ctrl + C, коли програма не виходить правильно через кнопку закриття GUI.
baptx

Відповіді:


22

Ваша killкоманда назад.

Як і багато команд UNIX, параметри, які починаються з мінусу, повинні бути першими, перш ніж інші аргументи.

Якщо ви пишете

kill -INT 0

він розглядає -INTяк варіант і надсилає SIGINTдо 0( 0це спеціальне число, яке означає всі процеси в поточній групі процесів).

Але якщо ти пишеш

kill 0 -INT

він бачить 0, вирішує, що немає більше варіантів, тому використовує SIGTERMза замовчуванням. І посилає це поточній групі процесів так само, як і ви

kill -TERM 0 -INT    

(Було б також спробувати відправити SIGTERMна -INT, що викличе синтаксичну помилку, але він посилає SIGTERMдо 0першого, і ніколи не отримує , що далеко.)

Отже, ваш основний сценарій - це отримання, SIGTERMперш ніж він запускає waitта echo DONE.

Додайте

trap 'echo got SIGTERM' TERM

вгорі, відразу після

trap 'killall' INT

і запустіть його ще раз, щоб довести це.

Як зазначає Стефан Шазелас, ваші діти ( process1тощо) SIGINTза замовчуванням ігнорують .

У будь-якому випадку, я думаю, що надсилання SIGTERMматиме більше сенсу.

Нарешті, я не впевнений, чи kill -process groupгарантовано спочатку піти до дітей. Ігнорування сигналів під час вимкнення може бути хорошою ідеєю.

Тому спробуйте це:

#!/bin/bash
trap 'killall' INT

killall() {
    trap '' INT TERM     # ignore INT and TERM while shutting down
    echo "**** Shutting down... ****"     # added double quotes
    kill -TERM 0         # fixed order, send TERM not INT
    wait
    echo DONE
}

./process1 &
./process2 &
./process3 &

cat # wait forever

Дякую, це працює! Здається, це було проблемою.
ковзнув

9

На жаль, команди, розпочаті у фоновому режимі, встановлюються оболонкою для того, щоб ігнорувати SIGINT, і ще гірше, вони не можуть скасувати її ігнорування trap. В іншому випадку все, що вам потрібно зробити, - це

(trap - INT; exec process1) &
(trap - INT; exec process2) &
trap '' INT
wait

Оскільки process1 та process2 отримають SIGINT при натисканні клавіші Ctrl-C, оскільки вони є частиною тієї самої групи процесів, яка є групою переднього плану терміналу.

Код, наведений вище, буде працювати з pdksh та zsh, які з цього приводу не відповідають POSIX.

З іншими оболонками вам доведеться використовувати щось інше, щоб відновити обробник за замовчуванням для SIGINT, наприклад:

perl -e '$SIG{INT}=DEFAULT; exec "process1"' &

або використовувати інший сигнал, як SIGTERM.


2

Для тих, хто просто хоче вбити процес і чекати його загибелі, але не на невизначений час :

Він чекає максимум 60 секунд на тип сигналу.

Попередження: ця відповідь жодним чином не пов’язана із захопленням сигналу вбивства та його відправленням.

# close_app_sub GREP_STATEMENT SIGNAL DURATION_SEC
# GREP_STATEMENT must not match itself!
close_app_sub() {
    APP_PID=$(ps -x | grep "$1" | grep -oP '^\s*\K[0-9]+' --color=never)
    if [ ! -z "$APP_PID" ]; then
        echo "App is open. Trying to close app (SIGNAL $2). Max $3sec."
        kill $2 "$APP_PID"
        WAIT_LOOP=0
        while ps -p "$APP_PID" > /dev/null 2>&1; do
            sleep 1
            WAIT_LOOP=$((WAIT_LOOP+1))
            if [ "$WAIT_LOOP" = "$3" ]; then
                break
            fi
        done
    fi
    APP_PID=$(ps -x | grep "$1" | grep -oP '^\s*\K[0-9]+' --color=never)
    if [ -z "$APP_PID" ]; then return 0; else return "$APP_PID"; fi
}

close_app() {
    close_app_sub "$1" "-HUP" "60"
    close_app_sub "$1" "-TERM" "60"
    close_app_sub "$1" "-SIGINT" "60"
    close_app_sub "$1" "-KILL" "60"
    return $?
}

close_app "[f]irefox"

Він вибирає додаток для вбивства на ім'я чи аргументи. Зберігайте дужки для першої літери імені програми, щоб уникнути відповідності самій грепі.

З деякими змінами ви можете безпосередньо використовувати PID або простіший pidof process_nameзамість psоператора.

Деталі коду: Остаточне значення має отримати PID без пробілів.


1

Якщо ви хочете керувати деякими фоновими процесами, чому б не використовувати функції керування роботою bash?

$ gedit &
[1] 2581
$ emacs &
[2] 2594
$ jobs
[1]-  Running                 gedit &
[2]+  Running                 emacs &
$ jobs -p
2581
2594
$ kill -2 `jobs -p`
$ jobs
[1]-  Interrupt               gedit
[2]+  Done                    emacs

0

Тож я і з цим пограбував. У Bash ви можете визначати функції та робити фантазійні речі з ними. Мої співробітники і я використовую terminator, тому для пакетних виконання ми зазвичай породимо цілу купу вікон термінатора, якщо ми хочемо переглянути вихід (менш елегантний, ніж tmuxвкладки, але ви можете використовувати більш інтерфейс, схожий на GUI, ха-ха).

Ось настройка, яку я придумав, а також приклад матеріалів, які ви можете запустити:

#!/bin/bash
custom()
{
    terun 'something cool' 'yes'
    run 'xclock'
    terun 'echoes' 'echo Hello; echo Goodbye; read'
    func()
    {
        local i=0
        while :
        do
            echo "Iter $i"
            let i+=1
            sleep 0.25
        done
    }
    export -f func
    terun 'custom func' 'func'
}

# [ Setup ]
run()
{
    "$@" &
    # Give process some time to start up so windows are sequential
    sleep 0.05
}
terun()
{
    run terminator -T "$1" -e "$2"
}
finish()
{
    procs="$(jobs -p)"
    echo "Kill: $procs"
    # Ignore process that are already dead
    kill $procs 2> /dev/null
}
trap 'finish' 2

custom

echo 'Press <Ctrl+C> to kill...'
wait

Запуск його

$ ./program_spawner.sh 
Press <Ctrl+C> to kill...
^CKill:  9835
9840
9844
9850
$ 

Редагуйте @Maxim, щойно побачила вашу пропозицію, що робить його набагато простішим! Спасибі!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.