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