Що відбувається, це те, що обидва 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цикл як приклад.)