Сценарии на языке командной оболочки получили самое широкое распространение, особенно написанные на языках, совместимых с bash. Но эти сценарии часто сложны и непонятны. Сложность — враг безопасности и причина неудобочитаемости кода. Эта книга на практических примерах покажет, как расшифровывать старые сценарии и писать новый код, максимально понятный и легко читаемый.
Авторы Карл Олбинг (Carl Albing) и Джей Пи Фоссен (JP Vossen) покажут, как использовать мощь и гибкость командной оболочки. Даже если вы умеете писать сценарии на bash, эта книга поможет расширить ваши знания и навыки. Независимо от используемой ОС — Linux, Unix, Windows или Mac — к концу книги вы научитесь понимать и писать сценарии на экспертном уровне. Это вам обязательно пригодится.
Вы познакомитесь с идиомами, которые следует использовать, и такими, которых следует избегать.
Разработка своего руководства по стилю
Главная тема этой книги — знакомство с идиоматическими приемами программирования на bash и правилами оформления кода. Надеемся, что нам удалось представить все необходимые для этого инструменты. Стиль — важный способ пояснить, как мы пишем код. Выберите рекомендации по стилю на свой вкус, запишите и придерживайтесь их.
В этой книге мы рассмотрели несколько важных аспектов оформления кода и другие рекомендации, о которых следует помнить при проектировании и разработке систем. Вы можете использовать эту главу в качестве отправной точки для создания своего собственного руководства по стилю или просто принять предложенное как есть, если оно вам нравится.
В приложении представлены дополнительные сведения без их обсуждения. Его можно использовать как справочник по стилю. Кроме того, материалы приложения с разметкой Markdown и HTML можно найти на странице этой книги в GitHub.
Запомните следующие основные принципы:
● Прежде всего — KISS (Keep It Simple, Stupid! — не будь глупцом, упрощай!). Сложность — враг безопасности, но она также затрудняет чтение и понимание кода. Конечно, при современных требованиях системы не могут быть простыми, но постарайтесь не делать их сложнее, чем требуется.
● Одно из следствий нарушения принципа KISS — усложнение отладки. Как сказал Брайан Керниган (Brian Kernighan): «Отладка в два раза сложнее, чем написание кода, поэтому, если вы пишете настолько сложный код, насколько способны, то по определению вы недостаточно умны, чтобы отладить его».
● Старайтесь не изобретать велосипед. Все, что вы задумали, скорее всего, уже было сделано раньше, и, вероятно, имеется подходящий готовый инструмент или библиотека. Если такой инструмент уже установлен, просто используйте его. Как бы вы ни старались, вы не сможете превзойти качество и надежность инструмента rsync, поэтому просто используйте его. Если вы на-шли подходящий код в интернете… что ж, можно подумать и о его использовании.
● Заранее планируйте особые случаи или переопределения, поскольку без них не обойтись. Возьмите из дистрибутива Linux файл /etc/thing/global.cfg с настройками по умолчанию, а затем реализуйте возможность переопределения настроек в каталоге /etc/thing/config.d/ или подобном ему. См. раздел «Настроечные каталоги» в главе 9.
● Код не существует вне системы управления версиями! Рано или поздно он будет утерян, и тогда его действительно не станет.
● Документируйте все (но не нужно писать книгу о своем сценарии). Пишите свой код, комментарии и документацию в расчете на то, что их будет читать человек, который присоединится к команде через год, когда вы забудете, почему вы сделали именно так. Документируйте приемы, которые не сработали, и почему, и особенно приемы, которые потенциально могут навредить (rm -rf /$undefined_variable оказалось по-настоящему плохой идеей!).
● Держите код и документацию «сухими»: не повторяйтесь. Несколько копий одного и того же кода обязательно рассинхронизируются — вопрос лишь в том, когда это произойдет.
Используйте функции и библиотеки; избегайте «сырости».
Положения «Дзен Python» применимы и к bash. Попробуйте выполнить команду python -c «import this» или загляните в документацию Python.
Руководство по стилю bash не переносится на другие командные оболочкиА если конкретно, что должно быть отражено в руководстве по стилю? В следующих разделах мы рассмотрим некоторые рекомендации.
Это руководство по стилю предназначено исключительно для bash, и его нельзя перенести на POSIX, Bourne, Dash и другие командные оболочки. Если вам придется писать сценарии для них, переработайте и адаптируйте рекомендации из этого руководства, чтобы учесть особенности синтаксиса и возможности этих оболочек.
Будьте особенно осторожны в Docker и других контейнеризаторах, где /bin/sh не ссылается на bash, а /bin/bash может вообще отсутствовать!
Это относится и к ограниченным окружениям, таким как системы интернета вещей и промышленные контроллеры. См. разделы «bash в контейнерах» в предисловии и «Shebang!» в главе 9.
Удобочитаемость
Удобочитаемость кода важна! Или, как говорят программисты на Python, читаемость имеет значение. Код пишется один раз, но вы (и другие), скорее всего, будете читать его много раз. Потратьте лишние секунды или минуты, подумайте о тех, кому придется читать этот код в следующем году… вполне вероятно, что это будете вы сами. Стремитесь к балансу между абстракцией (DRY — не повторяйся) и удобочитаемостью:
● KISS (Keep It Simple, Stupid! — не будь глупцом, упрощай!).
● Удобочитаемость: не старайся быть «умным», старайся быть ясным.
● Понятные имена имеют решающее значение!
● Всегда используйте заголовки.
● Если возможно, предусмотрите вывод полезной информации при получении -h, --help и неверных аргументов!
● Используйте встроенные документы (с отступами) вместо последовательностей инструкций echo со строками, потому что встроенный документ проще обновить и переформатировать.
● Для включения файлов с настройками, которые должны иметь расширение .cfg (или .conf, или любое другое, соответствующее вашим стандартам), используйте source вместо точки (.). Точку легко не заметить и сложнее найти.
● Если возможно, используйте даты в формате ISO-8601.
● Старайтесь упорядочивать элементы списков, это уменьшит вероятность дублирования, а также упростит добавление и удаление элементов. Примерами могут служить IP-адреса (используйте GNU-версию sort -V), имена хостов, пакеты для установки, операторы case и содержимое переменных или массивов/списков.
● Если возможно, используйте для облегчения понимания длинные ключи команд, например diff --quiet вместо diff -q, но следите за переносимостью на системы, отличные от GNU/Linux.
● Если какие-то ключи имеют только короткий или неочевидный вариант, добавьте поясняющий комментарий.
● Старайтесь документировать свой выбор: почему взят именно этот ключ и даже почему не подходят другие ключи.
● Обязательно документируйте ключи, применение которых заманчиво, но на самом деле вызывает проблемы, особенно если вы их часто используете.
Выбирая имена для переменных, старайтесь избегать чересчур общих понятий, таких как:
`${GREP} ${pattern} ${file}`
Эти имена слишком абстрактны, они могут использоваться повторно и в то же время практически не зависят от контекста. Давайте заменим обратные кавычки `` более современной, читаемой (как нам кажется) и определенно более пригодной к вложению конструкцией $(). Но самое важное — уберем лишние фигурные скобки ${} и используем осмысленные имена:
$($GREP "$re_process_errors" $critical_process_daily_log_file)
Через некоторое время вы обнаружите, что нашли общие с коллегами (осмелимся сказать) идиоматические способы выражения понятий и операций, с которыми вы часто сталкиваетесь. Значит, настал момент написать руководство по стилю, если у вас его еще нет.
Если же вы занимаетесь доработкой или сопровождением кода, то лучше следовать соглашениям, принятым в этом коде, или придется изменять стиль и, возможно, реорганизовывать его целиком.
Комментарии
О комментариях написано много: что, где, когда и т.д. Тем не менее, приведем некоторые рекомендации по оформлению комментариев:
● Всегда используйте заголовки.
● Пишите комментарии так, будто они предназначены для нового члена вашей команды, который придет через год.
● Комментируйте свои функции.
● Описывайте не что вы делаете или не делаете, а почему.
● Исключение: комментируйте, что вы делаете, когда код на bash труден для понимания.
● Добавляйте комментарии, описывающие ключи внешних программ, особенно если они короткие или неочевидные.
● Начинайте комментарий с заглавной буквы, но опускайте знаки пунктуации в конце, если только комментарий не состоит из нескольких предложений.
Добавляйте полезные комментарии, объясняющие, почему вы поступили именно так и каковы ваши намерения! Например:
continue # К следующему файлу в цикле for
В теории нет необходимости объяснять, что делается, если код написан достаточно понятно. Но иногда код на bash слишком запутан. Вот, например, прием с подстановкой переменных (см. «Удаление пути или префикса» в главе 4):
PROGRAM=${0##*/} # Аналог basename на bash
Логические блоки кода полезно выделять разделителями. Но не добавляйте закрывающий разделитель внизу — он лишь внесет ненужный «шум» и уменьшит количество полезных строк кода на экране. Что бы вы ни делали, не стройте рамки из символов со всех четырех сторон! Это совершенно не нужно. Вы будете тратить слишком много времени на «правильное оформление». Что еще хуже, впоследствии это будет мешать исправлять или обновлять комментарии, потому что тогда придется снова исправлять рамки.
Не делайте так:
#################################################################
# Не стройте такие рамки, как эта! #
# #
# В будущем, когда понадобится исправить комментарий, они #
# потребуют дополнительных усилий, чтобы сохранить форму рамки. #
# И это не самый плохой пример #
#################################################################
Делайте так:
#################################################################
# Оформляйте рамки, как здесь!
#
# Такие комментарии проще редактировать, потому что не приходится
# заботиться о выравнивании правой рамки. Если потребуется внести
# что-то новое, достаточно просто добавить начальный символ "#"
Имена
Важно выбирать говорящие имена. На момент написания кода, когда все детали еще свежи в памяти, разница между $file и $critical_process_daily_log_file не кажется существенной, кроме необходимости вводить лишние символы. Но мы гарантируем, что дополнительное время, потраченное на выбор и набор говорящих имен, окупится сторицей за счет снижения вероятности ошибок и уменьшения времени, которое потребуется в будущем, чтобы понять и улучшить код. Некоторые рекомендации по стилю, связанные с именами:
● Понятные имена имеют решающее значение!
● Имена глобальных переменных и констант записывайте ЗАГЛАВНЫМИ буквами.
● Старайтесь не изменять глобальные переменные, хотя иногда это упрощает код (KISS).
● Объявляйте константы с помощью readonly или declare -r.
● Имена других переменных записывайте строчными буквами.
● Для имен функций используйте Смешанный_Регистр.
● Не используйте ВерблюжийРегистр, заменяйте пробелы символом подчеркивания (_) и не забывайте, что дефис (-) нельзя использовать в именах переменных.
● Минимизируйте использование массивов bash, поскольку они часто сложны для чтения (см. главу 7). Во многих случаях хорошо работает конструкция for var in $regular_var.
● С начала сценария вместо $1, $2,… $N используйте переменные с говорящими именами. Такой код не только проще читать и отлаживать, он также более удобен для внедрения значений по умолчанию, добавления или реорганизации аргументов.
● Различайте типы ссылок, например $input_file и $input_dir.
● Используйте метки «FIXME» и «TODO» с именами и номерами заявок, если это уместно.
Подумайте, насколько легко перепутать или допустить опечатку и использовать неправильно следующие имена:
file1='/path/to/input'
file2='/path/to/output'
Намного проще, понятнее и менее подвержен ошибкам такой код:
input_file='/path/to/input'
output_file='/path/to/output'
Кроме того, не экономьте силы на ввод символов и не сокращайте имена: набирая имя $filename, сложнее ошибиться. А при использовании сокращений нередко потом трудно будет вспомнить, какое из них требуется: $filenm, $flname или $flnm?
Функции
Перейдем к рекомендациям по оформлению функций:
● Всегда используйте заголовки.
● Хорошие имена имеют решающее значение!
● Функции должны определяться до их использования.
● Группируйте функции в начале сценария и отделяйте их друг от друга двумя пустыми строками и разделяющими комментариями.
● Не размещайте между функциями блоки другого кода!
● Используйте комбинацию Верблюжьей_И_Змеиной_Нотаций, чтобы имена функций отличались от имен переменных.
● Используйте function My_Func_Name { вместо My_Func_Name() {, так как такое объявление понятнее и его проще отыскать с помощью команды grep -P '^\s*function '.
● Каждая функция должна содержать комментарий, описывающий, что она делает, какие данные принимает (включая глобальные) и какие данные выводит.
● Если в сценарии имеются полезные обособленные фрагменты кода или фрагменты, используемые многократно (а также похожие друг на друга), превратите их в функции. Если они востребованны в разных сценариях, как, например, журналирование или отправка электронной почты, подумайте о создании библиотеки — файла, который можно подключать к сценариям.
● Начинайте имена «библиотечных» функций с одинакового префикса, например подчеркивания (_) — _Log.
● Подумайте об использовании слов-заполнителей для удобства чтения, если это имеет смысл. Определите их как local junk1="$2" # Неиспользуемый заполнитель. Например: _Create_File_If_Needed "/path/to/$file" # содержит важное значение.
● Используйте встроенную команду local для определения локальных переменных в функциях. Но имейте в виду, что такие переменные маскируют неудачный код возврата, поэтому объявляйте и инициализируйте их в отдельных строках, используя подстановку команд. Например, вначале local my_input, затем my_input="$(some-command)".
● Функции, насчитывающие более 25 строк кода, закрывайте комментарием } # Конец функции MyFuncName, чтобы упростить поиск конца функции на экране. Для функций короче 25 строк это необязательно, но не возбраняется, если такие комментарии не загромождают код.
● Объявление функции main в большинстве случаев не требуется.
● Использование main имеет смысл для программистов на Python и C, или если код используется также в качестве библиотеки и такая функция нужна для модульного тестирования.
● Отделяйте основной код от блока определения функций двумя пустыми строками и разделительным комментарием, особенно если функций много.
Определите в своей библиотеке одну функцию для журналирования (например, _Log) и используйте ее! В противном случае есть риск получить дикую смесь из функций журналирования, стилей и ссылок. В идеале, как мы уже говорили, используйте журналирование в syslog и позвольте операционной системе самой позаботиться о месте назначения, ротации журналов и т.д.
Кавычки
Кавычки не вызывают сложностей до определенного момента. Мы знаем многие их особенности, но все равно иногда действуем методом проб и ошибок, особенно когда пытаемся создать однострочный интерфейс для запуска команды от имени другого пользователя через sudo или на другом узле через ssh. Добавьте несколько инструкций echo и будьте внимательны. Ниже перечислены рекомендации, связанные с кавычками:
● Заключайте в кавычки переменные и строки, потому что это выделяет их и поясняет ваши намерения, но не используйте кавычки, если они загромождают код или мешают расширению.
● Не заключайте в кавычки целые числа.
● Используйте одинарные кавычки, если не требуется интерполяция.
● Не используйте конструкцию ${var} без необходимости, чтобы не загромождать код. Необходима она, например, в таких случаях, как ${variable}_suffix или ${being_lower_cased,,}.
● Заключайте в кавычки подстановку команд, например var="$(command)".
● Всегда заключайте в кавычки обе части выражения любого оператора проверки, например [[ "$foo" == 'bar' ]]. Исключения:
● Если одна часть выражения является целым числом.
● Если используется ~=, потому что регулярные выражения нельзя заключать в кавычки!
● Заключайте в одинарные кавычки переменные внутри инструкций echo, например echo «cd to '$DIR' failed», потому что это поможет определить переменные, которые оказались неинициализированными или пустыми.
● Другой вариант — echo «cd to [$DIR] failed», если так вам больше нравится.
● Если переменная не определена, то при использовании set -u вы получите сообщение об ошибке. Но не в случае, если она определена и имеет пустое значение.
● Старайтесь использовать одинарные кавычки в строках формата команды printf (см. подраздел «Вывод POSIX» и остальную часть раздела «Функция printf» в главе 6).
Форматирование
Рекомендации, связанные с форматированием кода:
● Не пренебрегайте отступами! Несколько лишних пробелов, как правило, не имеют значения в bash (за исключением пробелов вокруг =), но отступы в командах, составляющих один блок, облегчают понимание и позволяют увидеть сходства и различия.
● Не оставляйте пробелов в конце строк! Такие пробелы вызовут лишний шум в системе управления версиями.
● Оформляйте отступы четырьмя пробелами, а во встроенных документах — табуляцией.
● Переносите длинные строки примерно на 78-м знаке (включая пробелы). В строке, следующей за переносом, сделайте дополнительный отступ на два пробела. Разрывайте строки непосредственно перед символом | или >, чтобы они были сразу заметны при просмотре кода.
● Код, открывающий блок, размещайте в одной строке, например:
if expression; then
for expression; do
● В элементах списка case..esac оформляйте дополнительный отступ в четыре пробела и закрывайте их ;; с тем же отступом. Блоки кода в каждом элементе должны иметь дополнительный отступ в четыре пробела.
● Однострочные элементы следует закрывать символами ;; в той же строке.
● Предпочтительнее выравнивать отступы по ) в каждом элементе, если это не загромождает код.
● Правильное форматирование иллюстрирует пример 8.4 на с. 119.
Многие считают ненужным разбивать строки шириной более 70–80 знаков (с пробелами), предполагая, что все используют широкие дисплеи и интегрированные среды разработки. Во-первых, в зависимости от команды и личных предпочтений человека это не всегда соответствует действительности. Во-вторых, даже если это верно, когда действительно что-то случится и вам придется заняться отладкой в vi и консоли 80 × 24, вы не оцените строки кода длиной 200+.
Переносите строки.
Переносите их непосредственно перед важной частью, чтобы при беглом просмотре, когда взгляд скользит вдоль левого края, продолжения были сразу же заметны. Мы добавляем половину обычного отступа после переноса. В книге сложно привести хороший (плохой?) пример длинной строки, потому что она будет перенесена при меньшей длине, чем 70–80 знаков. Но есть простой и, на наш взгляд, читаемый пример:
... Много разного кода, с отступами...
/long/path/to/some/interesting/command \
| grep "$re_stuff_we_care_about" \
| grep -v "$re_stuff_to_ignore" \
| /path/to/email_or_log_command ...
...еще масса кода
Синтаксис
Рекомендации, связанные с синтаксисом:
● В начале сценария укажите #!/bin/bash — или #!/usr/bin/env bash, но не #!/bin/sh.
● Используйте $@, если вы не уверены, что вам действительно нужна $*.
● Для проверки равенства используйте == вместо =, чтобы избежать путаницы с присваиванием.
● Вместо обратных кавычек и обратных апострофов используйте $().
● Используйте [[ вместо [, если только вам не нужна [ для пере-носимости, например в dash.
● Для целочисленной арифметики применяйте (( )) и $(( )) и избегайте let и expr.
● Используйте [[ expression ]] && block или [[ expression ]] || block, если такой код легко читается. Не применяйте [[ expression ]] && block || block, потому что этот код может сделать не то, что вы думаете; используйте для этого if… then… (elif ..) else… fi.
● Попробуйте использовать строгий режим (см. раздел «Строгий режим bash» в главе 9).
● set -euo pipefail предотвратит или поможет обнаружить многие простые ошибки.
● IFS=$'\n\t' применяйте с осторожностью (а лучше вообще избегайте этой команды).
Другие рекомендации
Дополнительные рекомендации:
● В «системных» сценариях используйте журналирование в syslog и позвольте операционной системе самой позаботиться о пункте назначения, ротации журналов и т.д.
● Сообщения об ошибках должны выводиться в STDERR, например echo 'A Bad Thing happened' 1>&2.
● Проверяйте доступность внешних инструментов с помощью [ -x /path/to/tool ] || {… блок обработки ошибки… }.
● Когда что-то не получается, должны выводиться информативные сообщения.
● Определите коды выхода в операторах exit, особенно в случае выхода по ошибке.
___
Более подробно с книгой можно ознакомиться на сайте издательства:
» Оглавление
» Отрывок
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Для Хаброжителей скидка 25% по купону — Bash
Комментарии (9)
d_garry
13.04.2023 14:27+7уберем лишние фигурные скобки ${}
....
Не используйте конструкцию ${var} без необходимости, чтобы не загромождать кодКак обычно, сколько людей-столько мнений. И у shellcheck'а тоже своё собственное мнение. Неужели "загромождение" кода двумя лишними скобками хуже, чем гадание "что хотел сказать автор" - "$var_suffix" или "${var}_suffix" ? Или же "_suffix" появился позже, а скобки просто поставить забыли?
alef13
13.04.2023 14:27+4Вот взяли и всю книгу пересказали :)
а вообще abs потолще раза в 4 и бесплатная к тому же.13werwolf13
13.04.2023 14:27"потошще" не всегда весомый аргумент, оно может быть толше из-за литров воды или из-за полезной информации.. в общем надо посмотреть.
можно ссылочку на abs а то яннп что это, хочется пополнить свою коллекциюgreysd
13.04.2023 14:27+2Вмешаюсь. Это же advanced bash scripting, все о ней знают, кто пишет на баш.
13werwolf13
13.04.2023 14:27+1
MonsterCatz
13.04.2023 14:27IFS=$'\n\t' применяйте с осторожностью (а лучше вообще избегайте этой команды).
Может кто рассказать, почему лучше избегать?
jobber_man
13.04.2023 14:27Независимо от используемой ОС ... Windows ... bash
На дворе пока только 2023 год, какой, блин, bash в Windows? Максимум korn shell, и то, нужно отдельно ставить Windows Services for UNIX.
Можно, конечно, найти bash от 3-rd party vendors, типа MSYS, MinGW или Ubuntu, но для настройки Windows это мало чем поможет.
comdivuz
13.04.2023 14:27Ну как бы вот у нас проекты и в них есть баш скрипты,например гит хуки никто не пишет их бат версий. На винду всегда встаёт git и всегда он ставится с мингв башем, idea и vs code тоже и на винде настраиваются на баш в качестве терминала. Причём тут настройка windows? Так что ничего криминального в фразе не вижу. Ну и кроме этого ведь есть ещё маки, не линуксовые юниксы и проч. И дескать к ним тоже применимы идиомы, чем плохо?
prishol
Полезные "вредные" советы!