Bash — предустановленная командная оболочка в современных операционных системах на основе Unix. Ее используют в скриптах (сценариях) для автоматизации повторяющихся задач командной строки.
Даже минимального синтаксиса Bash достаточно для запуска других программ и работы с их выходным кодом и данными. Однако сегодня интерпретаторы Bash включают еще и полнофункциональный командный язык с основными функциями универсального языка программирования. Благодаря этому они позволяют писать хорошо читаемые сценарии оболочки, включая традиционные вызовы командной строки и алгоритмический код.
Новые версии Bash предоставляют ассоциативные массивы и такие связанные с производительностью функции, как передача по ссылке. Все это делает оболочку конкурентоспособной среди языков для написания скриптов.
Рассмотрим некоторые методы программирования на Bash, позволяющие писать быстродействующие и легко читаемые скрипты. Такие методы позволяют писать на Bash программы как общего назначения, так и служебные, а также алгоритмы, включая их прототипирование, и даже применять Bash в соревнованиях по программированию!
Использование массивов в сценариях оболочки
Традиционная переменная в Bash обычно не имеет типа, но ее можно обрабатывать как целое, десятичное число или строку в соответствии с конкретным контекстом. Обычно такие переменные используют для хранения выходных данных команд, параметров алгоритма и других временных значений. Bash также поддерживает два типа массивов: одномерные (с числовым индексом) и ассоциативные (со структурой ключ-значение). Работать с массивами в Bash так же просто, как и в других популярных языках общего назначения с динамической типизацией, таких как Python, PHP и JavaScript.
Вот как создаются массивы в Bash:
#!/bin/bash
numbers=(2 4 5 2)
declare -a words
words[0]='Orange'
words[1]='Pineapple'
echo ${numbers[@]} ${words[@]}
Приведенный выше код выводит массив:
Проверить объявление каждой ссылки на массив можно с помощью встроенного declare
:
Короткими записями можно выполнять обработку массивов и такие манипуляции, как добавление новых, удаление существующих, обработка, сортировка элементов массива и т. д. Например, следующий код печатает три лучшие оценки и удаляет недопустимые значения:
#!/bin/bash
declare -a marks
marks+=(75 65 80 102 26) # метки класса A
marks+=(103 68) # метки класса B
# Удаление недопустимых меток
for i in "${!marks[@]}"; do
if ((marks[i] > 100)); then
unset "marks[$i]"
fi
done
# Сортировка всех меток
marks_s=($(printf '%s\n' "${marks[@]}" | sort -nr))
# Печать top-3
echo ${marks_s[0]} ${marks_s[1]} ${marks_s[2]}
Приведенный код порождает отдельный процесс для сортировки, поскольку использована внешняя команда sort
. Но этого можно избежать, реализуя простой алгоритм сортировки, такой как selection sort
, дополненный кодом Bash.
Создание карт и словарей
В некоторых сценариях командной оболочки приходится хранить данные пары «ключ-значение». Такие структуры данных программисты обычно используют при создании словарей, карт и контейнеров кэширования (посредством мемоизации). Встроенные структуры для хранения данных «ключ-значение» таких сценариев есть в Python. А как использовать структуру словаря в Bash?
Функция ассоциативного массива для хранения данных ключ-значение появилась в Bash с версии 4.0. Вот простой пример ассоциативных массивов Bash:
#!/bin/bash
declare -A marks=([john]=75 [doe]=82 [ann]=83 [ava]=72)
for key in "${!marks[@]}"; do
printf "$key \t ${marks[$key]} \n"
done
Здесь использован синтаксис !mapvar[@]
для извлечения всех ключей словаря в виде массива для итерации. Приведенный выше код печатает все ключи и значения:
Для манипуляции данными ассоциативного массива и доступа к ним в Bash имеется минимальный синтаксис. Работа с ассоциативными массивами Bash аналогична работе со словарями Python. Вот пример:
#!/bin/bash
read -p "Enter coords (i.e., [x]=10 [y]=12): " coords
declare -A "coords=($coords)"
if [ ! -v "coords[x]" ]; then
coords[x]=5
fi
if [ ! -v "coords[y]" ]; then
coords[y]=10
fi
for key in "${!coords[@]}"; do
printf "$key = ${coords[$key]} \n"
done
Приведенный исходный код запрашивает у пользователя координаты x
и y
, устанавливает значения по умолчанию для отсутствующих значений осей и печатает их на терминале. Здесь использован синтаксис ! -v
, аналогичный not in
, обычно используемому со словарями Python.
Реализация поддержки именованных аргументов
Выполняя сценарий оболочки через интерпретатор Bash, операционная система создает новый процесс Bash с файлом сценария в качестве первого аргумента командной строки. Операционные системы обычно позволяют передавать последовательность аргументов в каждый процесс.
Предоставляя аргументы командной строки для других команд/процессов, вы также можете передать их в сценарий Bash. Предположим, что нужно передать в скрипт два целочисленных значения. Затем можно легко использовать $1
и $2
для доступа к значениям первого и второго аргумента соответственно. Но все усложняется, когда используется больше индексированных аргументов и нужно реализовать необязательные аргументы (также известные как флаги или параметры командной строки).
В качестве решения для этого сценария можно использовать именованные аргументы со встроенной функцией getopts
. С помощью следующего сценария оболочки можно переопределить некоторые значения в сценарии по умолчанию:
#!/bin/bash
title="Information"
message="Hello world"
while getopts ":t:m:" option; do
echo $option
case "${option}" in
t)
title=${OPTARG}
;;
m)
message=${OPTARG}
;;
esac
done
zenity --info --title="$title" --text="$message"
По умолчанию приведенный выше сценарий показывает окно сообщения GTK с заголовком и сообщением по умолчанию, но их можно переопределить с помощью именованных аргументов командной строки, как показано ниже:
Встроенная функция getopts
поддерживает использование только однобуквенных опций. Можно использовать getopt
для использования длинных опций (например, — title
), как показано в этом Gist.
Использование в функциях передачи по ссылке
Передача по ссылке — особенность языка программирования, позволяющая передавать данные в функции через ссылки на память, не копируя целые сегменты данных в новые переменные. Программисты на C++ всегда стремятся писать производительный код, используя для этого передачу по ссылке, а не передачу по значению для объектов классов, структур и строк.
В сценариях оболочки Bash, начиная с версии 4.3, можно использовать для передачи ссылки на имена. Вот простой пример фрагмента кода, который изменяет строковую переменную с помощью функции:
#!/bin/bash
function change_str_var() {
local str_new="Bash"
local -n str_ref=$1
echo "$str_ref -> $str_new" # Python -> Bash
str_ref=$str_new
}
str="Python"
change_str_var str
echo $str # Bash
Приведенная выше функция change_str_var
создает локальную ссылку str_ref
на глобальную строку str
с помощью команды local
. Затем она присваивает новое строковое значение, переопределяя старое значение.
Некоторые программисты используют внутри функции команду echo
и вызывают конкретную функцию с помощью подстановки команд, чтобы вернуть значение из функции Bash (поскольку нативное ключевое слово return
в Bash поддерживает только возврат допустимого выходного кода). Это порождает еще одну дочернюю оболочку с потреблением дополнительных ресурсов. Таким образом, используя передачу по ссылке, в новой версии Bash можно писать более производительные возвраты из функции.
Использование атрибутов типа и модификатора для переменных
Как известно, Bash — нетипизированный командный язык. Другими словами, переменные данные он обычно обрабатывает как строки, но в соответствии с контекстом (т. е. арифметическими расширениями). С другой стороны, Bash также позволяет использовать атрибуты типов и предоставляет два встроенных типа массивов. Даже с этими особенностями невозможно идентифицировать Bash как чисто динамически типизированный язык. Но эти переменные атрибуты помещают Bash где-то посередине между нетипизированным и динамически типизированным языками.
С помощью атрибута целочисленной переменной Bash обеспечивает пометку конкретной переменной как целого числа. После создания целочисленной переменной и присвоения ей дробного значения, Bash выдает подобное предупреждение:
Bash также позволяет создавать константы с помощью команды declare -r
. Когда сценарий пытается изменить константу, Bash выводит на экран сообщение об ошибке. Более того, можно создавать массивы с помощью встроенной команды declare
, как уже отмечалось выше.
Bash также позволяет добавлять к переменным некоторые атрибуты-модификаторы. Например, следующим образом можно создать строки, содержащие только строчные или только прописные буквы:
#!/bin/bash
declare -l lc_str="Hello World"
declare -u uc_str
uc_str="Hello"
uc_str="World"
echo $lc_str # hello world
echo $uc_str # WORLD
С атрибутами переменных Bash можно писать более надежные, легко читаемые, современные сценарии оболочки.
Читайте также:
- Практикум по созданию сценариев командной строки
- Как ускорить навигацию командной строки
- Написание консольных скриптов: Bash против Python
Читайте нас в Telegram, VK и Дзен
Перевод статьи Shalitha Suranga: 5 Bash Coding Techniques That Every Programmer Should Know