Обработка сигналов в сценариях оболочки






Сигналы — это числовые сообщения, отправляемые запущенным приложениям операционной системой, другими приложениями или пользователем. Сигналы, как правило, ожидают от приложения какой-то определённой реакции, например «корректно заверши работу», «приостановись, чтобы я мог тебя перевести в фоновый режим» или «умри!».


Обычно для отправки сигналов приложениям используется программа kill, но некоторые сигналы можно отправлять и при помощи клавиатурных комбинаций, например таких, как знакомые многим  Ctrl+C или Ctrl+Z.

Сигналы обрабатываются «каскадно». То есть, сигнал отправляется приложению и, если приложение не обработало поступивший сигнал, то он возвращается обратно оболочке или операционной системе. Некоторые типы сигналов в принципе не могут обрабатываться приложениями. Например, сигнал SIGKILL вообще не доставляется приложению, а перехватывается операционной системой, которая немедленно завершает работу приложения, которому сигнал был адресован.

Давайте начнём изучение сигналов с того, что узнаем, какие они бывают. Для этого можно воспользоваться командой kill -l:

Большинство перечисленных сигналов нам неинтересны и используются редко. Среди часто используемых я бы отметил следующие. SIGHUP — обычно отправляется в момент выхода пользователя из системы; SIGINT (именно он посылается приложению при нажатии Ctrl+C) - запрос прервать работу; SIGKILL — немедленное завершение работы процесса операционной системой; SIGTSTP — отправляется комбинацией Ctrl+Z; SIGCONT — это тот сигнал, который отправляется приложению из оболочки командами fg и bg после приостановки его работы сигналом SIGTSTP; SIGWINCH используется в оконных системах для отправки приложению уведомления об изменении размеров окна; сигналы SIGUSR1 и SIGUSR2 используются для организации межпроцессного взаимодействия.

Давайте, наконец, попробуем написать немного кода. В сценариях оболочки сигналы перехватываются при помощи встроенной команды trap. Ниже на примере показано, как обычно это делается:

Как это использовать в сценарии? Очень просто!

Видите здесь бесконечный цикл? Он практически не использует ресурсов, поскольку практически всё время «спит» и будет выполняться вечно или до тех пор, пока вы не остановите его.

Давайте рассмотрим более гибкий способ работы с сигналами в сценариях оболочки при помощи функций:

Запустите приведённый выше код и из другого терминала попробуйте отправить ему несколько разных сигналов:

Ниже показано, как скрипт будет реагировать на отправленные ему сигналы:

Вооружившись этим примером, давайте посмотрим, как обрабатывать более сложные сигналы, например отправляемые по нажатию Ctrl+Z.

Создавая сложные скрипты, вы обнаружите, что попадается масса моментов, когда необходимо игнорировать поступивший сигнал TSTP (он же SIGTSTP, Ctrl+Z или сигнал номер 18), а иногда — перезапустить работу сценария.

Начиная решать подобную задачу, сперва создадим функцию, которая не только обрабатывает определённый сигнал, но и затем «отключает» сама себя и не будет срабатывать при последующих вызовах:

Вызовом trap — TSTP в любом месте скрипта вы отключаете назначенный ранее обработчик указанного сигнала. Теперь, если у нас в коде есть строка

в том месте, где мы хотим включить игнорирование нажатия Ctrl+Z, то она заставит скрипт не реагировать на Ctrl+Z, но только один раз. В следующий раз, когда вы нажмёте Ctrl+Z, сигнал будет обработан так же, как обычно.

Игнорирование Ctrl+Z часто используется тогда, когда ваш скрипт ещё не готов «правильно» на него реагировать. Просто вставьте этот код, там где требуется такое поведение сценария:

Затем, когда  ваш скрипт будет готов к обработке Ctrl+Z, используйте следующую конструкцию, которая вернёт обычную реакцию сценария на сигнал останова:

Эксперименты показывают, что имеет место быть какая-то странноватая буферизация вывода терминалом, связанная с сигналом SIGTSTP, так что не удивляйтесь, если, перехватывая этот сигнал, вы вдруг не получите вывод от вашего скрипта вплоть до момента его завершения.

Давайте рассмотрим более практичный пример. Представим, что у вас есть некий административный скрипт, который работает в режиме демона. Иногда у вас возникает необходимость внести какие-либо изменения в его конфигурационный файл, но при этом нужно сделать так, чтобы файл конфигурации перечитывался скриптом в процессе его работы, а не только при запуске. Такая возможность позволяет значительно сократить время неработоспособности демона, поскольку его не нужно останавливать, а затем заново запускать.

При решении этой задачи мы будем использовать сигнал SIGUSR1, поскольку он обычно для этого и используется.

Считывание файла конфигурации может быть реализовано достаточно просто:

Вспомним, что использование команды «точка» приведёт к тому, что переменные, определённые во внешнем файле, станут доступными в текущей оболочке. Вместо точки можно использовать команду source.

Вот так выглядит наш экспериментальный скрипт:

Начнём с того, что определим в нашем конфигурационном файле переменную number со значением 5. Затем, через 10-15 секунд изменим значение переменной на 1. До тех пор, пока мы не пошлём скрипту сигнал USR1, он будет выводить начальное значение переменной:

Тем временем, когда значение переменной в конфигурационном файле уже изменено, из другого терминала пошлём нашему скрипту сигнал:

Теперь посмотрим, что происходит в терминале, где выполняется наш скрипт:

Классно, не правда ли?

Надеюсь, что сегодняшнее наше исследование работы с сигналами в сценариях оболочки пригодится вам в работе. Лично я многому научился, пока разбирался со всем, о чём здесь написано. Однако, для меня всё ещё остаётся загадкой, каким образом возобновить вывод скрипта после того, как он перехватывает сигнал SIGTSTP и я надеюсь, что читатели подскажут мне, как это сделать.

По мотивам LinuxJournal.Com