Приветствую вас, дорогие друзья! Иногда требуется прерваться. Например, в том случае, если какая-то программа, вызванная к жизни из недр вашего скрипта вдруг подвисает и отказывается завершать свою работу в ожидаемые разумные сроки. Или если по каким-либо случайным причинам родной до боли код в ходе тестирования вдруг "подвешивает" скрипт в бесконечном цикле.
При возникновении подобного рода проблем не очень грамотно написанные скрипты попросту "замрут", создав возможно маленькие, а возможно и очень серьёзные сбои в работе системы.
Что же нужно для того, чтобы написать свой код правильно, с учётом периодов разумного ожидания завершения (таймаутов в английском варианте :)) отдельных участков?
BASH-программистам (не побоюсь этого слова), которые пользуются стандартными утилитами проекта GNU, нужно знать по крайней мере две ключевых команды, позволяющих так или иначе вывести ваш код из состояния зимней спячки: at и timeout. BASH-программистам-полиглотам, которые пишут под любые системы, необходимо ещё знать команду doalarm.
Утилита timeout в соответствии со своиv названием предназначена только для завершения по истечению определённого интервала времени целого скрипта. at - гораздо более гибкая (хотя и есть у неё существенный недостаток, о чём - ниже), так что о ней мы и поговорим, оставив обсуждение timeout и её близкого родственника doalarm до выхода расширенного и дополненного варианта этой статьи.
Если вы не сталкивались с этой командой раньше, то будете удивлены тому, что at вообще-то сама по себе ничего не делает! Она всего лишь выполняет функции регистратора, принимающего заявки, формирующего соотв. "задания" и передающего их исполнительной части - процессу-демону atd, - с указанием времени начала выполнения.
Демон atd периодически просматривает очередь порученных ему заданий и выполняет их в нужный момент, после чего удаляет соотв. записи из очереди поручений регистратора. Текст заданий можно передавать команде at либо в потоке стандартного ввода stdin, либо через файл.
Последний метод подходит лишь для достаточно специфических случаев, в нашем случае будем использовать stdin. Указать время выполнения команды можно также различными способами (см. man at), но дело в том, что в общем случае мы не можем знать, когда и во сколько будет запущен наш скрипт, поэтому можем оперировать лишь относительными величинами, показывающими, сколько времени должно пройти с момента обращения к команде at для того, чтобы нужное нам задание выполнилось.
Для этого, к счастью, не требуется в коде скрипта вычислять даты самому (при помощи date), а можно просто указать тот самый "момент X" в формате now+N, где now - указывает на необходимость взять текущий момент за точку отсчёта, а +N - на то, сколько минут, часов, дней, недель и т.д., следует "отсчитать".
Обратите внимание, на то, что минимальной дискретной единицей времени для демона atd является одна минута. На мой взгляд, это один из главных недостатков данного ПО, так же, собственно, как и его близкого родственника - планировщика Cron. Ведь одна минута... много это или мало?
Скорее всего это меньше того времени, которое мог бы отнять у вас и вашей системы "заснувший" скрипт, но с другой стороны это во многих случаях слишком много для ожидания завершения команды или цикла, в обычном случае отрабатывающих за несколько секунд или даже за доли секунды.
Как же пользоваться at в нашем случае? Дабы не растекаться мыслью по древу, приведу один наглядный пример:
#!/bin/bash |
Из комментариев надеюсь, понятно, что делает скрипт. Конструкция "<<<" позволяет передавать в stdin текст, не пользуясь более медленной конструкцией "echo SOME_TEXT |" (и аналогичными). Запоминать значение PID процесса в переменной не обязательно, это просто здравый расчёт на то, что данное значение нам ещё понадобиться при дальнешей доработке скрипта, да и просто иметь дело с "говорящими" переменными приятнее. Обратите особое внимание, что пути к исполняемым файлам в задании для atd должны указываться явным образом!
Представленный код работает "идеально", если скрипт завершается нештатно извне, но если "всё идёт по плану", то он, к сожалению, оставит за собой немытую посуду и ошмётки грязи в прихожей, то есть - оставит задание для ATD, которое никогда не сможет быть выполнено.
Ещё хуже, если задание не будет отменено при нормальной работе кода, в этом случае вы получите "нож в спину" в виде нежданно-негаданного сигнала TERM тогда, когда этого совсем не ждёте.
Соответственно, нам нужно будет убрать это задание из очереди в случае нормального завершения чреватого "засыпаниями" участка кода. Делается это очень просто, давайте изменим код следующим образом:
#!/bin/bash |
А что, если нам нужно не просто завершить работу скрипта, но ещё и обработать исключительную ситуацию его нештатной работы? Всё очень просто, посылайте скрипту командной kill определённый сигнал, предварительно перехватывая его, для этого добавьте после строки #!/bin/bash следующие строки:
sigTimeout='SIGHUP' |
А команде kill передайте параметр "-s $sigTimeout".
Вуаля! Теперь вы можете использовать полученные знания для переписывания зачастую весьма корявых скриптов начальной загрузки с целью постичь Дзен, а также кардинально улучшить свой любимый дистрибутив, или для того, чтобы просто в ходе особо длительных операций наподобие копирования жёсткого диска по сети рассказывать своим пользователям анекдоты.
А теперь даю установку: если вам понравилась эта статься, отпишитесь в комментариях и ждите следующую, идея которой уже есть, тестовый код тоже, осталось только понять, что ж я накодил и описать это доступным языком.
Да пребудет с вами Сила!