Безопасное выход из цикла в bash
Скажем, у меня есть сценарий bash, который делает:
while : do foo done
Я хотел бы иметь возможность запускать этот скрипт с консоли и иметь возможность выйти из него в произвольное время, пока это происходит между двумя прогонами foo. Поэтому, если, скажем, я нажимаю Ctrl + C (это может быть другое действие, из-за которого скрипт должен выйти, Ctrl + C – просто пример), который выйдет в следующей доступной точке после выполнения foo:
- Как запустить сценарий при обновлении папки в Solaris 5.10?
- Менее конкретный вход для скрипта
- пусть скрипт найдет путь сам по себе
- Добавить одноразовую запланированную задачу через сценарий оболочки?
- Измените файл без создания другого файла
while : do foo if [pressed_ctrl_c]: break done
2 Solutions collect form web for “Безопасное выход из цикла в bash”
Вы можете попробовать такую конструкцию:
#!/bin/bash # INTR= trap 'INTR=yes; echo "** INTR **" >&2' INT while : do ( # Protect the subshell block trap '' INT # Protected code here echo -n "The date/time is: " sleep 2 date read -t2 -p 'Continue (y/n)? ' YN || echo test n = "$YN" && echo "Asked for BREAK" >&2 && exit 90 ) SS=$? test 90 -eq $SS && echo "Matched BREAK" >&2 && break # Ctrl/C, perhaps? test yes = "$INTR" && echo "Matched INTR" >&2 && break done exit 0
Некоторые примечания
-
read
иtest
пара демонстрирует интерактивное управление защищенным сегментом кода внутри блока( ... )
. -
exit 90
является эквивалентомbreak
но изнутри подоболочки.test 0 != $? ...
test 0 != $? ...
после того, как конец блока подоболочки будет завершен, чтобы зафиксировать статусexit 90
и реализоватьbreak
который действительно нужен коду. - Подоболочка может использовать разные значения статуса выхода для указания различных типов требуемого потока управления (
break
,exit
и т. Д.). - Это не мешает программе устанавливать собственный обработчик сигнала. Например,
gdb
устанавливает собственный обработчик дляSIGINT
( Ctrl C ). Если целью является предотвращение выхода пользователя из сеанса, изменение ключа прерывания может помочь устранить ситуацию (см. Код ниже). Неэлегантный, но потенциально эффективный.
Изменение ключа SIGINT на терминале
G=$(stty -g) # Save settings test -n "$G" && stty intr ^A # That is caret and A, not Ctrl/A # ... SIGINT generated with Ctrl/A rather than Ctrl/C ... test -n "$G" && stty "$G" # Restore original settings
Это похоже на работу:
#!/bin/sh pressed_ctrl_c= trap "pressed_ctrl_c=1" INT while true do (trap "" INT; foo)& wait || wait if [ "$pressed_ctrl_c" ] then # echo break fi done
- Инициализировать
pressed_ctrl_c
в значение null. Это, вероятно, не обязательно в скрипте. -
trap command signum
сообщает оболочке, что она настроена на фиксацию сигнального номераsignum
и выполняетcommand
когда она ее ловит. «INT» сокращен для «SIGINT», который является коротким для «сигнала прерывания», который является техническим термином для сигнала, генерируемого Ctrl + C , поэтому приpressed_ctrl_c
Ctrl + C оболочкиpressed_ctrl_c
на 1. (SIGINT имеет числовое значение 2, поэтому вы можете сказатьtrap "pressed_ctrl_c=1" 2
если вы хотите сэкономить при вводе текста.) - Внутри цикла у нас есть командная строка в круглых скобках. Это создает суб-оболочку.
- Мы снова используем команду
trap
. Этаcommand
времени является пустой строкой; это говорит оболочке игнорировать сигнальное числоsignum
. Поскольку это находится в круглых скобках, это влияет только на подоболочку. - Запустить
foo
. Поскольку он был запущен из подоболочки,foo
будет игнорировать Ctrl + C , т. Е. Он будет продолжать работать, даже если вы егоfoo
. - Поместите подоболочку на задний план ….
- … и дождитесь его завершения.
- Если команда
wait
успешно, перейдите к операторуif
. Если это не удается, выполните другой. (Я вернусь к этому.) - Если
$pressed_ctrl_c
установлено, вырваться из цикла. Необязательно раскомментируйте командуecho
если Ctrl + C появляется на вашем терминале как^C
и вы хотите перейти к следующей строке.
Мы запускаем команду в фоновом режиме, а затем сразу же wait
ее. Это очень похоже на запуск команды на переднем плане (по крайней мере, когда это делается в скрипте). Команда wait
завершится успешно, когда подоболочка завершится; т.е. когда команда foo
завершается. (Команда wait
будет успешно завершена, даже если foo
вернет ошибку.) Когда первая команда wait
завершается успешно, мы пропускаем вторую и переходим к if
.
Оболочка, которая запускает цикл, перехватывает прерывания, но подоболочка и, следовательно, процесс foo
игнорируют их. Итак, когда вы pressed_ctrl_c
Ctrl + C , оболочка устанавливает pressed_ctrl_c
в 1 и прерывает команду (первый) wait
. Поскольку первая команда wait
не удалась, мы переходим ко второму. Помните, что foo
по-прежнему работает, поэтому это wait
все еще есть чем заняться (т. Е. Он будет ждать завершения foo
).
Наконец, если переменная была установлена, чтобы указать, что Ctrl + C была нажата во время работы foo
, отключите вывод цикла.
Если вы дважды нажмете Ctrl + C , вторая будет прервать и прервать вторую команду wait
. Это приведет к тому, что ваш скрипт завершится и вернет вас в приглашение вашей оболочки, оставив foo
в фоновом режиме. Вы можете смягчить это, сказав wait || wait || wait || wait
wait || wait || wait || wait
wait || wait || wait || wait
; расширяя его, насколько вы хотите. Вам нужно будет набирать Ctrl + C один раз для каждого wait
преждевременно завершая сценарий.
D'о!
Проблема в том, что процессы, которые помещаются в фоновом режиме с помощью сценария, имеют свой стандартный вход, установленный в /dev/null
. Если ваш foo
читается с клавиатуры, то выше будет нуждаться в пересмотре.