Bash: Почему родительский script не заканчивается на SIGINT, когда дочерние script ловушки SIGINT?

script1.sh:

 #!/bin/bash    

./script2.sh
 echo after-script

script2.sh:

#!/bin/bash

function handler {
  exit 130
}

trap handler SIGINT

while true; do true; done

Когда я запускаю script1.sh из терминала, а затем использую Ctrl + C для отправки SIGINT в свою группу процессов, сигнал захватывается скриптом2.sh, и когда script2.sh завершается, script1.sh печатает "after- script". Тем не менее, я бы ожидал, что script1.sh немедленно завершится после строки, которая вызывает script2.sh. Почему это не так в этом примере?

Дополнительные примечания (править):

  • Поскольку сценарии1.sh и script2.sh находятся в одной группе процессов, SIGINT отправляется в оба сценария, когда в командной строке нажата комбинация клавиш Ctrl + C. Вот почему я не ожидал, что script1.sh продолжит работу при выводе script2.sh.

  • Когда строка "обработчик ловушки SIGINT" в скрипте2.sh закомментирована, script1.sh завершает работу сразу после того, как script2.sh существует. Я хочу знать, почему он ведет себя по-другому, поскольку script2.sh создает только тот же код выхода (130).

Ответ 1

Новый ответ:

Этот вопрос гораздо интереснее, чем я подозревал. Ответ здесь дан здесь:

Что происходит с SIGINT (^ C) при отправке в perl script, содержащий детей?

Вот соответствующий лакомый кусочек. Я понимаю, что вы не используете Perl, но я предполагаю, что Bash использует соглашение C.

Встроенная системная функция Perls работает так же, как система C (3) функции из стандартной библиотеки C в отношении сигналов. Если вы используете Perl-версию системы() или open или backticks, то родительский - одна вызывающая система, а не вызываемая это - будет IGNORE любой SIGINT и SIGQUIT, пока дети работает.

Это объяснение - лучшее, что я видел о различных вариантах, которые могут быть сделаны. В нем также говорится, что Bash использует WCE. То есть, когда родительский процесс получает SIGINT, он ждет, пока его дочерний процесс не вернется. Если этот процесс обрабатывается из SIGINT, он также выходит из SIGINT. Если ребенок вышел каким-либо другим способом, он игнорирует SIGINT.

Существует также способ, которым вызывающая оболочка может определить, вызвана ли вызываемая программа вышла из SIGINT, и если она игнорирует SIGINT (или используется для другие цели). Как и в способе WUE, оболочка ждет, пока ребенок полный. Он показывает, закончилась ли программа на SIGINT, и если поэтому он прекращает действие script. Если программа выполнила любой другой выход, script будет продолжен. Я буду называть способ делать вещи "WCE" (для "ожидания и совместного выхода" ) для остальной части этого документа.

Я не могу найти ссылку на это в man-странице Bash, но я буду продолжать искать в информационных документах. Но я на 99% уверен, что это правильный ответ.

Старый ответ:

Ненулевой статус выхода из команды в Bash script не завершает работу программы. Если вы выполните echo $? после ./script2.sh, он покажет 130. Вы можете завершить script, используя set -e, как предлагает phs.

$ help set
...
-e  Exit immediately if a command exits with a non-zero status.

Ответ 2

Вторая часть обновленного ответа @seanmcl верна, и ссылка на http://www.cons.org/cracauer/sigint.html действительно хорошая, чтобы читать внимательно.

Из этой ссылки: "Вы не можете" подделать "правильный статус выхода с помощью выхода (3) со специальным числовым значением, даже если вы посмотрите числовое значение для вашей системы". Фактически, это то, что предпринимается в @Hermann Speiche script2.sh.

Один ответ - изменить обработчик функции в файле script2.sh следующим образом:

function handler {
  # ... do stuff ...
  trap INT
  kill -2 $$
}

Это эффективно удаляет обработчик сигнала и "перезапускает" SIGINT, заставляя процесс bash выйти с соответствующими флагами, чтобы его родительский процесс bash затем правильно обрабатывал SIGINT, который был первоначально отправлен на него. Таким образом, использование set -e или любого другого хака на самом деле не требуется.

Также стоит отметить, что если у вас есть исполняемый файл, который неправильно ведет себя при отправке SIGINT (он не соответствует "Как быть надлежащей программой" в приведенной выше ссылке, например, он выходит с нормальным кодом возврата), один из способов обойти это состоит в том, чтобы связать вызов этого процесса с script следующим образом:

#!/bin/bash

function handler {
  trap INT
  kill -2 $$
}

trap handler INT
badprocess "[email protected]"

Ответ 3

Причина в том, что ваш script1.sh не завершается, так это то, что script2.sh работает в подоболочке. Чтобы сделать первый выход script, вы можете либо установить -e, как предложено phs и seanmcl, либо принудительно запустить script2.sh в той же оболочке, указав:

. ./script2.sh

в вашем первом script. То, что вы наблюдаете, было бы очевидно, если вы должны были сделать set -x перед выполнением вашего script. help set сообщает:

  -x  Print commands and their arguments as they are executed.

Ответ 4

Вы также можете отправить второй script завершающий сигнал на свой родительский script с помощью SIGHUP или других безопасных и полезных сигналов, таких как SIGQUIT, в которых родительский script может рассмотреть или заманить ловушку (отправка SIGINT не будет работа).

script1.sh:

#!/bin/bash

trap 'exit 0' SIQUIT  ## We could also just accept SIGHUP if we like without traps but that sends a message to the screen.

./script2.sh  ## or "bash script.sh" or "( . ./script.sh; ) which would run it on another process
echo after-script

script2.sh:

#!/bin/bash

SLEEPPID=''

PID=$BASHPID
read PPID_ < <(exec ps -p "$PID" -o "$ppid=")

function handler {
  [[ -n $SLEEPPID ]] && kill -s SIGTERM "$SLEEPPID" &>/dev/null
  kill -s SIGQUIT "$PPID_"
  exit 130
}

trap handler SIGINT

# better do some sleeping:

for (( ;; )); do
  [[ -n $SLEEPPID ]] && kill -s 0 "$SLEEPPID" &>/dev/null || {
    sleep 20 &
    SLEEPPID=$!
  }
  wait
done

Ваша первоначальная последняя строка в вашем скрипте1.sh также может быть так же, как и в зависимости от вашей реализации сценариев.

./script2.sh || exit
...

или

./script2.sh
[[ $? -eq 130 ]] && exit
...