То , что происходит в том , что как 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цикл в качестве примера.)