Webrium
Подписаться

Скрипт на shell, который не сломается

Shell по умолчанию продолжает работу после ошибки. Четыре строки в начале файла переводят скрипт из режима «молча делает не то» в режим «останавливается и говорит».

Без настройки останова скрипт продолжает работу после неудачной командыошибка

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

Тупик: считать, что скрипт слишком мал для аккуратности

Первое, что пробуют, — написать быстро и не усложнять: проверки, обработка ошибок и кавычки кажутся избыточными для тридцати строк.

Это не помогает по одной причине: shell по умолчанию продолжает выполнение после ошибки. Команда не нашла файл, вернула ненулевой код — скрипт идёт дальше и делает следующий шаг так, как будто предыдущий удался. В обычном языке это было бы исключением и остановкой; здесь это тишина.

Отсюда самый частый способ потерять данные скриптом: переменная оказалась пустой, путь собрался неправильно, и удаление отработало не там, где предполагалось. Каждый шаг по отдельности сработал корректно.

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

Четыре строки, которые меняют поведение

Три настройки в начале файла превращают shell из «продолжаем несмотря ни на что» в «останавливаемся на первой проблеме».

Останов при ошибке. Скрипт прекращает работу, как только команда вернула ненулевой код. Это ровно то поведение, которого от него ожидают все, кто читает.

Останов при обращении к необъявленной переменной. Опечатка в имени перестаёт превращаться в пустую строку. Именно эта настройка предотвращает историю с удалением не там.

Останов при ошибке внутри конвейера. Без неё код возврата берётся от последней команды в цепочке, и ошибка первой теряется.

Разделитель полей. Значение по умолчанию разбивает строки по пробелам, из-за чего имена файлов с пробелами превращаются в несколько аргументов. Ограничение разделителя переводом строки убирает целый класс ошибок.

Эти четыре строки не делают скрипт хорошим, но переводят его из режима «молча делает не то» в режим «останавливается и говорит».

У останова при ошибке есть известные исключения, и о них лучше знать заранее, чем удивляться. Команда внутри условия не считается ошибкой — иначе любая проверка останавливала бы скрипт. Команда, результат которой присваивается переменной в объявлении, тоже теряет свой код возврата. Оба случая обходятся явной проверкой, и оба стоит держать в голове: именно в них настройка молчит там, где от неё ждут срабатывания.

Кавычки — не украшение

Подстановка переменной без кавычек разбивается по пробелам и раскрывает шаблоны имён файлов. Это самая частая причина того, что скрипт работает у автора и ломается у коллеги: у автора в путях не было пробелов.

Правило простое: кавычки ставятся всегда, отсутствие кавычек — осознанное решение, а не экономия символов. То же касается подстановки результата команды: без кавычек её результат разберётся на слова.

Отдельная история — аргументы, начинающиеся с дефиса. Файл с таким именем команда примет за ключ. Разделитель, отмечающий конец ключей, снимает это, и в скриптах, работающих с чужими именами файлов, он обязателен.

Что делает скрипт пригодным для чужих рук

Проверка входных данных в начале. Не в середине, где переменная используется, а сразу: есть ли обязательные аргументы, существует ли каталог, доступна ли команда, на которую скрипт рассчитывает. Ошибка на первой секунде дешевле ошибки на середине работы.

Сообщения об ошибках в поток ошибок, а не в обычный вывод. Иначе сообщение попадёт в файл, куда перенаправлен результат, и не попадёт человеку.

Уборка за собой. Временные файлы удаляются обработчиком выхода, а не последней строкой скрипта: последняя строка не выполнится, если скрипт остановился раньше.

Идемпотентность там, где возможно. Скрипт, который можно запустить дважды без вреда, чинится перезапуском. Скрипт, который при повторном запуске делает вторую половину работы дважды, чинится разбирательством.

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

Понятный вывод при успехе. Скрипт, который молчит, невозможно отличить от скрипта, который не запустился. Одна строка о том, что и куда было сделано, экономит проверку вручную — особенно когда запуск идёт по расписанию и смотреть на него никто не будет.

Код возврата, которому можно верить. Скрипт, всегда возвращающий ноль, ломает всё, что стоит после него в конвейере: следующий шаг считает работу успешной. Это цена одной строки в конце и причина половины ситуаций, когда сбой замечают через сутки.

Где заканчивается shell

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

Признак, что пора менять инструмент: в скрипте появился разбор вывода другой команды. Обычно это означает, что данные, которые вы разбираете, где-то существуют в структурированном виде, и вы восстанавливаете их из текста, предназначенного для человека.

Второй признак — длина. Сотня строк ещё читается, три сотни уже нет: в shell нет ни типов, ни областей видимости, помогающих удержать такой объём.

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

Позиция редакции

Мы считаем, что любой скрипт, от которого зависит процесс, начинается с настроек останова и кавычек вокруг подстановок, и что это дешевле любых проверок постфактум. Практический вывод: если скрипт лежит в репозитории и запускается не только автором, эти четыре строки обязательны — они превращают тихие ошибки в громкие.

Мы неправы, если это разовая команда в истории оболочки: там ничего из перечисленного не нужно, потому что автор смотрит на результат своими глазами прямо сейчас. Граница проходит в момент, когда скрипт сохранили в файл: с этого момента у него появляется второй читатель, даже если это вы через полгода.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Вложенные слои — поколения модных инструментов; в самой глубине выделен старый механизм, то, что осталось живым.старые механизмы
Практика

Деплой без кластера: что осталось живым

Как выкатывать без кластера: рабочая схема выкладки и то, что вы на самом деле теряете, отказавшись от оркестратора.

Шесть столбцов потерь времени в терминале: видимые потери — нажатия, опечатки, повторяемые пути — низкие, а невидимые — вспомнить команду, найти каталог, восстаЧасы — не в наборе
Усиление

Терминал, который экономит часы, а не минуты

Усиливает вывод про инструменты: выигрыш даёт не скорость набора, а история, поиск и повторное использование сделанного.

Выражение не сокращают, а разбирают на именованные части — как кодстрокисовпало
Углубление

Регулярное выражение, которое можно поддерживать

Глубже про выражения: что делает их читаемыми спустя годы и где регулярные выражения перестают быть подходящим инструментом.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть