Bash: почему Родительский скрипт не завершается на SIGINT, когда дочерний скрипт ловит 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 в свою группу процессов, сигнал захватывается script2.sh и когда script2.sh прекращается, script1.sh печать "after-script". Тем не менее, я ожидал script1.sh для немедленного завершения после строки, вызывающей script2.sh - ... Почему это не так в данном примере?

дополнительные замечания (edit):

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

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

4 ответов


ответ:

этот вопрос гораздо интереснее, чем я изначально предполагал. Ответ по существу ДАН здесь:

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

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

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

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

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

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

ответ:

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

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

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

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

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

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

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

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

#!/bin/bash

function handler {
  trap INT
  kill -2 $$
}

trap handler INT
badprocess "$@"

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

. ./script2.sh

в вашем первом скрипте. То, что вы наблюдаете, было бы очевидно, если бы вы сделали set -x перед выполнением сценария. help set говорит:

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

вы также можете позволить вашему второму скрипту отправить завершающий сигнал на своем родительском скрипте SIGHUP или другие безопасные и полезные сигналы, такие как SIGQUIT, в которых родительский скрипт может рассмотреть или ловушку (отправка 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

ваш оригинальный последней строки в script1.sh мог просто точно так же в зависимости от ваших скриптов реализация.

./script2.sh || exit
...

или

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