Писать Bash-скрипты может быть сложно, пока не изучите нюансы.
Важно помнить, что Bash — это скриптовый язык. Это означает, что в нем вы не найдете стандартного функционала, предлагаемого языками программирования.
- Нет нативной поддержки объектно-ориентированного программирования.
- Нет внешних библиотек типа requests в Python или axios в Node, но можно пользоваться внешними приложениями, такими как curl.
- Типизация переменных не поддерживается, все значения оцениваются как строки. Но благодаря специальным командам можно использовать числа. Например, можно проверять равенство при помощи
-eq
и инкрементировать переменную при помощи((VAR_NAME+1))
. Вместе с тем в Bash есть «слабый» способ объявления типа переменных — с применением команды declare. - Начиная с версии 4.4 в Bash поддерживаются ассоциативные массивы, напоминающие словари в Python или объекты в JavaScript. При этом важно помнить, что в macOS по умолчанию стоит Bash v3.2.
- Здесь нет соглашений о нейминге, к которым можно было бы обращаться, как к источнику истины. Как писать глобальные переменные? Pascal_Case? snake_case? SCREAMING_SNAKE_CASE? Нет никаких жестких правил.
Как вы уже поняли, Bash-программисты, если таковые есть, сталкиваются со многими проблемами. Приведенный выше список — лишь верхушка айсберга. Вот несколько постов, авторы которых разделяют это мнение:
Должно быть, понятно, что я просто влюблен в Bash. Этим чувством я и поделюсь с вами в своей статье.
Соглашение об именах переменных
Когда я пишу Bash-скрипты, то при написании имен переменных использую следующие стили:
Тип | Область видимости | Соглашение |
---|---|---|
Переменная окружения | глобальная | MY_VARIABLE |
Глобальная переменная | глобальная | _MY_VARIABLE |
Локальная переменная | функция | my_variable |
Читая мои старые Bash-скрипты, довольно сложно сразу определить, что за переменная перед тобой. Применение правил нейминга, приведенных в таблице, помогает с первого взгляда определять область видимости переменной и ее назначение.
Приложение Good Vibes
Разумеется, мы рассмотрим несколько практических примеров. Стилистические правила, которых я придерживаюсь, я использовал в специальном скрипте good_vibes.sh.
good_vibes.sh:
#!/usr/bin/env bash # ^ This is called a Shebang # I'll cover it in future blog posts # Global variables are initialized by Env Vars. # I'm setting a default value with "${VAR_NAME:-"DEFAULT_VALUE"}" _USER_NAME="${USER_NAME:-"$USER"}" _USER_AGE="${USER_AGE:-""}" complement_name(){ local name="$1" echo "Wow, ${name}, you have a beautiful name!" } complement_age(){ local name="$1" local age="$2" if [[ "$age" -gt "30" ]]; then echo "Seriously ${name}? I thought you were $((age-7))" else echo "Such a weird age, are you sure it's a number?" fi } main(){ # The only function that is not "pure" # This function is tightly coupled to the script complement_name "$_USER_NAME" complement_age "$_USER_NAME" "$_USER_AGE" } # Invokes the main function main
good_vibes.sh — запуск и результат:
export USER_NAME="Julia" USER_AGE="36" && \ bash good_vibes.sh # Output Wow, Julia, you have a beautiful name! Seriously Julia? I thought you were 29
Давайте разобьем мое приложение good_vibes.sh на набор правил, которые вы сможете применять в своих скриптах.
Расстояние между блоками кода
Благодаря двум пустым строкам после каждого блока кода скрипт становится более читаемым.
Отступы
Я делаю отступы в 2 пробела, хотя можно использовать и 4. Главное — выбрать какой-то один вариант и придерживаться его.
Фигурные скобки
Если переменная соединяется со строкой — ${VARIABLE} concatenated with string
— берите ее в фигурные скобки. Так код будет легче читать.
Если у вас просто одинокая переменная — $LONELY_VARIABLE
— нет необходимости в фигурных скобках. Благодаря разным подходам вы сможете с первого взгляда определять, одинокая это переменная или нет.
Изначальное предназначение фигурных скобок — осуществление расширения параметров оболочки. В good_vibes.sh это показано в разделе инициализации глобальных переменных.
Квадратные скобки
Благодаря использованию двойных квадратных скобок [[ ]]
облегчается чтение блоков условий. Но имейте в виду, что использование двойных квадратных скобок не поддерживается в Shell sh. Там нужно использовать одинарные квадратные скобки [ ]
.
Вот вам блок «сложных» условий для демонстрации читаемости:
if [[ "$USER_NAME" = "Julia" || "$USER_NAME" = "Willy" ]] \ && [[ "$USER_AGE" -gt "30" ]]; then echo "Easy to read right?" fi # Mind that `||` is replaced with `-o`, see https://acloudguru.com/blog/engineering/conditions-in-bash-scripting-if-statements # Thank you William Pursell if [ "$USER_NAME" = "Julia" -o "$USER_NAME" = "Willy" ] \ && [ "$USER_AGE" -gt "30" ]; then echo "No idea why but I feel lost with single brackets." fi
На случай, если вы вдруг не заметили, мы только что изучили, что ||
означает OR, а &&
— AND. Короткое выражение -gt
при использовании чисел означает greater than («больше, чем»).
Наконец, обратная косая черта \
позволяет делать перенос строк, чтобы код был более читаемым.
Примечание. Использование \
с последующим пробелом (\ <- extra space
) может привести к досадным ошибкам. Следите за тем, чтобы после бэкслэша не было лишних пробелов.
Мне кажется, что использование [[ ]]
дает большую интуитивность, потому что большинство условных команд — сдвоенные символы: &&
, ||
.
Инициализация переменных
Глобальные переменные инициализируются при помощи переменных окружения. Им устанавливается дефолтное значение — на случай, если переменная окружения окажется пустой.
Как упоминалось в комментариях в good_vibes.sh, я устанавливаю значение по умолчанию:
"${VAR_NAME:-"DEFAULT_VALUE"}"
В этом сниппете захардкожен текст DEFAULT_VALUE. Его можно заменить переменной. Например:
_USER_NAME="${USER_NAME:-"$USER"}"
Функции и локальные переменные
Имена функций и локальных переменных пишутся в snake_case. При желании можно использовать lowerCamelCase для имен функций.
Привязка функции к скрипту — большая ошибка. Я сам грешу этим время от времени, так что в моих функциях можно заметить глобальные переменные или переменные окружения. Но это только в тех случаях, если я точно знаю, что этот кусок кода будет редко меняться.
Ах да, ни в коем случае не используйте $1 и прочие аргументы напрямую. Вместо этого используйте local var_name="$1"
.
_USER_NAME="${USER_NAME:-"$USER"}" # Bad - coupled coupled_username(){ echo "_USER_NAME = ${_USER_NAME}" } # Good - decoupled decoupled_username(){ local name="$1" echo "name = ${name}" } # Usage coupled_username decoupled_username "$_USER_NAME"
Функциональное программирование
Эта тема связана с предыдущим разделом, где функции настолько «чисты», насколько это возможно. Как видно в good_vibes.sh, в функцию заключено практически все, за исключением инициализации глобальных переменных.
Я не вижу смысла писать функцию init_vars
, назначение которой — работа с глобальными переменными. Но периодически я добавляю функцию validate_vars
, которая проходится по глобальным переменным и проверяет их значения. Я уверен, что тут есть пространство для обсуждения, так что делитесь своим мнением в комментариях.
Заключение
Скрипт Good Vibes вобрал в себя практически все правила, которых я придерживаюсь, чтобы писать читабельные Bash-скрипты в парадигме функционального программирования.
Если вам кажется, что имена функциям и переменным нужно давать как-то иначе, — вперед! Главное — чтобы ваш код было легко понять, а остальное лишь детали.
Перевод статьи «Writing Bash Scripts Like A Pro — Part 1 — Styling Guide».
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]