Пишем Bash-скрипты: руководство по стилю

0
2
views

Писать 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».

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here