То , что происходит в том , что как bash
и ping
получить SIGINT ( bash
будучи не интерактивными, как ping
и bash
работать в одной и той же группы процессов , которая была создана и установленной в группе процессов переднего плана терминала в интерактивной оболочке запускался скрипт с).
Однако bash
обрабатывает этот SIGINT асинхронно, только после завершения работающей в данный момент команды. bash
выход происходит только после получения этого SIGINT, если текущая выполняемая команда умирает от SIGINT (т. е. ее состояние выхода указывает, что она была уничтожена SIGINT).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Выше bash
, sh
и sleep
получить SIGINT , когда я нажимаю Ctrl-C, но sh
выходит обычно с кодом 0 на выходе, поэтому bash
игнорирует SIGINT, поэтому мы видим , «здесь».
ping
По крайней мере, один из iputils, ведет себя так. При прерывании он печатает статистику и завершает работу со статусом выхода 0 или 1 в зависимости от того, были ли получены ответы на его запросы. Таким образом, когда вы нажимаете Ctrl-C во время ping
работы, bash
заметки, которые вы нажимали Ctrl-C
в его обработчиках SIGINT, но, поскольку он ping
завершается нормально, bash
не завершаются.
Если вы добавите sleep 1
в этот цикл и нажмете Ctrl-C
пока sleep
выполняется, потому что sleep
не имеет специального обработчика на SIGINT, он умрет и сообщит, bash
что он умер от SIGINT, и в этом случае bash
выйдет (на самом деле он убьет себя с SIGINT, так что сообщить о прерывании его родителю).
Относительно того, почему bash
ведет себя так, я не уверен, и я отмечаю, что поведение не всегда детерминировано. Я только что задал вопрос в bash
списке рассылки для разработчиков ( Обновление : @Jilles теперь нашел причину в своем ответе ).
Единственная другая оболочка, которая, как я обнаружил, ведет себя аналогично, это ksh93 (обновление, как упомянуто @Jilles, так же, как и FreeBSDsh
). Там, SIGINT, кажется, явно игнорируется. И ksh93
выходит всякий раз, когда SIGINT убивает команду.
Вы получаете то же поведение, что и bash
выше, но также:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Не выводит «тест». То есть он завершает свою работу (убивая себя там с помощью SIGINT), если команда, которой он ожидал, умирает от SIGINT, даже если она сама не получила этот SIGINT.
Обходной путь будет сделать добавить:
trap 'exit 130' INT
В верхней части скрипта необходимо принудительно bash
завершить работу после получения SIGINT (обратите внимание, что в любом случае SIGINT не будет обрабатываться синхронно, только после завершения текущей выполняемой команды).
В идеале мы хотели бы сообщить нашему родителю, что мы умерли от SIGINT (так что, если это, bash
например, другой сценарий, этот bash
сценарий также прерывается). Выполнение операции - exit 130
это не то же самое, что умирание SIGINT (хотя для некоторых оболочек будет установлено $?
одинаковое значение в обоих случаях), однако оно часто используется для сообщения о смерти SIGINT (в системах, где SIGINT равен 2, что является наибольшим).
Однако для bash
, ksh93
или FreeBSD sh
, это не работает. Этот 130 статус выхода не рассматривается SIGINT как смерть, и родительский сценарий не прервет его там.
Поэтому, возможно, лучшей альтернативой было бы убить себя с помощью SIGINT при получении SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Если пользователь вводит Ctrl + C во время редактирования одного из файлов,vi
просто отображается сообщение. Представляется разумным, что цикл должен продолжаться после того, как пользователь закончит редактирование файла. (И да, я знаю, что вы могли бы сказатьvi *.txt; cp *.txt newdir
; я просто представляюfor
цикл в качестве примера.)