То , что происходит в том , что как 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
цикл в качестве примера.)vi
(vim
по крайней мере, по крайней мере) отключает ttyisig
при редактировании (:!cmd
хотя это не очевидно, когда вы запускаете , и это очень применимо в этом случае).ping
выходит с 0 после получения SIGINT. Я обнаружил аналогичное поведение, когда скрипт bash содержитsudo
вместоping
, ноsudo
завершается с 1 после получения SIGINT. unix.stackexchange.com/questions/479023/…Объяснение заключается в том, что bash реализует WCE (ожидание и совместный выход) для SIGINT и SIGQUIT по адресу http://www.cons.org/cracauer/sigint.html . Это означает, что если bash получает SIGINT или SIGQUIT в ожидании выхода из процесса, он будет ожидать выхода из процесса и сам выйдет, если процесс завершится по этому сигналу. Это гарантирует, что программы, которые используют SIGINT или SIGQUIT в своем пользовательском интерфейсе, будут работать должным образом (если сигнал не вызвал завершение программы, сценарий продолжится в обычном режиме).
Недостатком является появление программ, которые перехватывают SIGINT или SIGQUIT, но затем завершают работу из-за этого, но используют обычный метод exit () вместо повторной отправки сигнала самим себе. Может быть невозможно прервать сценарии, которые вызывают такие программы. Я думаю, что реальное исправление существует в таких программах, как ping и ping6.
Подобное поведение реализовано в ksh93 и FreeBSD / bin / sh, но не в большинстве других оболочек.
источник
exit(130)
если вы прерываетеmksh -c 'sleep 10;:'
).Как вы предполагаете, это связано с отправкой SIGINT подчиненному процессу и продолжением работы оболочки после завершения этого процесса.
Чтобы справиться с этим лучше, вы можете проверить состояние завершения команд, которые выполняются. Код возврата Unix кодирует как метод, которым завершился процесс (системный вызов или сигнал), так и какое значение было передано
exit()
или какой сигнал завершил процесс. Все это довольно сложно, но самый быстрый способ использовать его - это знать, что процесс, который был прерван сигналом, будет иметь ненулевой код возврата. Таким образом, если вы проверите код возврата в своем скрипте, вы можете выйти самостоятельно, если дочерний процесс был прерван, устраняя необходимость в неэлегансах, таких как ненужныеsleep
вызовы. Быстрый способ сделать это на протяжении всего сценария - использовать егоset -e
, хотя для команд, чей статус выхода является ненулевым, может потребоваться несколько настроек.источник
ping
возвращается с состоянием выхода 0 после получения SIGINT, аbash
затем игнорирует SIGINT, который он получил сам, если это так. Добавление «set -e» или проверка состояния выхода здесь не помогут. Добавление явной ловушки на SIGINT поможет.Терминал замечает control-c и отправляет
INT
сигнал в группу процессов переднего плана, которая здесь включает в себя оболочку, посколькуping
не создал новую группу процессов переднего плана. Это легко проверить с помощью захватаINT
.Если выполняемая команда создала новую группу процессов переднего плана, то control-c перейдет к этой группе процессов, а не к оболочке. В этом случае оболочка должна будет проверить коды выхода, так как терминал не будет сигнализировать об этом.
(
INT
Обращение в оболочках может быть неправдоподобно сложным, кстати, так как оболочка иногда нужно игнорировать сигнал, а иногда и не Источник погружение , если любопытно, или вдумайтесь:.tail -f /etc/passwd; echo foo
)источник
ping
у меня нет причины запускать новую группу процессов здесь, и версия ping (iputils в Debian), с которой я могу воспроизвести проблему OP, не создает группу процессов.Ну, я попытался добавить
sleep 1
в сценарий bash, и взрыва!Теперь я могу остановить это с двумя Ctrl+C.
При нажатии Ctrl+C, A
SIGINT
сигнал посылается на данный момент процесса, выполняемого команда , которая была запущена внутри цикла. Затем процесс subshell продолжает выполнение следующей команды в цикле, которая запускает другой процесс. Чтобы иметь возможность остановить сценарий, необходимо отправить дваSIGINT
сигнала: один для прерывания текущей команды при выполнении и один для прерывания процесса подоболочки .В сценарии без
sleep
вызова нажатие Ctrl+Cдействительно быстрое и много раз, кажется, не работает, и невозможно выйти из цикла. Я предполагаю, что двойное нажатие недостаточно быстро, чтобы сделать его как раз в нужный момент между прерыванием текущего выполненного процесса и началом следующего. Каждое Ctrl+Cнажатие отправляетSIGINT
процесс, выполняемый внутри цикла, но ни в подоболочку .В сценарии с
sleep 1
этим вызовом выполнение будет приостановлено на одну секунду, а при прерывании первой Ctrl+C(первойSIGINT
) подоболочке потребуется больше времени для выполнения следующей команды. Итак, теперь второй Ctrl+C(второйSIGINT
) перейдет в подоболочку , и выполнение скрипта закончится.источник
Попробуй это:
Теперь измените первую строку на:
и попробуйте еще раз - посмотрите, не прерывается ли пинг.
источник
например
pgrep -f firefox
, grep PID выполненияfirefox
и сохранит этот PID в файл с именемany_file_name
. Команда sed добавитkill
в начало номера PID в файле any_file_name имя. Третья строка будетany_file_name
файл исполняемого файла. Теперь четвертая строка уничтожит PID, доступный в файлеany_file_name
. Запись вышеупомянутых четырех строк в файл и выполнение этого файла может сделать Control- C. Работает абсолютно нормально для меня.источник
Если кто-то заинтересован в исправлении этого
bash
функции, а не в философии, стоящей за ней , вот предложение:Не запускайте проблемную команду напрямую, но из оболочки, которая а) ожидает ее завершения, б) не связывается с сигналами и в) делает не реализует сам механизм WCE, а просто умирает при получении а
SIGINT
.Такую обертку можно сделать с помощью
awk
+ своейsystem()
функции.Вставьте в сценарий, как ОП:
источник
Вы - жертва хорошо известной ошибки bash. Bash делает jobcontrol для скриптов, что является ошибкой.
В результате bash запускает внешние программы в группе процессов, отличной от той, что используется для самого скрипта. Поскольку для группы процессов TTY задана группа процессов текущего переднего плана, уничтожается только этот передний процесс, и цикл в сценарии оболочки продолжается.
Чтобы проверить: выберите и скомпилируйте недавнюю оболочку Bourne, которая реализует pgrp (1) как встроенную программу, затем добавьте / bin / sleep 100 (или / usr / bin / sleep в зависимости от вашей платформы) в цикл сценария и затем запустите Борн Шелл. После того, как вы использовали ps (1) для получения идентификаторов процесса для команды sleep и bash, который запускает сценарий, вызовите
pgrp <pid>
и замените «<pid>» идентификатором процесса sleep и bash, который запускает сценарий. Вы увидите разные идентификаторы группы процессов. Теперь вызовите что-то вродеpgrp < /dev/pts/7
(замените имя tty на tty, используемое сценарием), чтобы получить текущую группу процессов tty. Группа процессов TTY равна группе процессов команды sleep.Чтобы исправить: используйте другую оболочку.
Последние источники Bourne Shell находятся в моем пакете инструментов schily, который вы можете найти здесь:
http://sourceforge.net/projects/schilytools/files/
источник
bash
это? AFAIKbash
делает это только если вы передаете опцию -m или -i.bash -c 'ps -j; ps -j; ps -j'
)./bin/sh -ce
. Мне пришлось добавить некрасивый обходной путь,smake
который явно убивает группу процессов для текущей запущенной команды, чтобы позволить^C
прервать многослойный вызов make. Вы проверяли, изменил ли bash группу процессов по идентификатору группы процессов, с которым она была инициирована?ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'
действительно сообщает один и тот же pgid для ps и bash во всех вызовах 3 ps. (ARGV0 = sh - этоzsh
способ передать argv [0]).