- PVSM.RU - https://www.pvsm.ru -

Когда вы автоматизируете какую-либо задачу, например, упаковываете свое приложение для Docker, то часто сталкиваетесь с написанием shell-скриптов. У вас может быть bash-скрипт для управления процессом упаковки и другой скрипт в качестве точки входа в контейнер. По мере возрастающей сложности при упаковке меняется и ваш shell-скрипт.
Все работает хорошо.
И вот однажды shell-скрипт совершает что-то совсем неправильное.
Тогда вы осознаете свою ошибку: bash, и вообще shell-скрипты, в основном, по умолчанию не работают. Если с самого начала не проявить особую осторожность, любой shell-скрипт достигнув определенного уровня сложности почти гарантированно будет глючным... а доработка функций корректности будет довольно затруднительна.
Давайте сосредоточимся на bash в качестве конкретного примера.
Рассмотрим следующий shell-скрипт:
#!/bin/bash
touch newfile
cp newfil newfile2 # Deliberate typo
echo "Success"
Как вы думаете, что произойдет, когда мы его запустим?
$ bash bad1.sh
cp: cannot stat 'newfil': No such file or directory
Success
Скрипт продолжал выполняться, даже если команда завершилась неудачно! Сравните это с Python, где исключение не позволяет выполнить последующий код.
Вы можете решить эту проблему, добавив set -e в начало shell-скрипта:
#!/bin/bash
set -e
touch newfile
cp newfil newfile2 # Deliberate typo, don't omit!
echo "Success"
А теперь:
$ bash bad1.sh
cp: cannot stat 'newfil': No such file or directory
Далее рассмотрим следующий скрипт, который пытается добавить каталог в переменную окружения PATH. PATH — это способ определения местоположения исполняемых файлов.
#!/bin/bash
set -e
export PATH="venv/bin:$PTH" # Typo is deliberate
ls
Когда мы его запускаем:
$ bash bad2.sh
bad2.sh: line 4: ls: command not found
Он не может найти ls, потому что мы допустили опечатку, написав $PTH вместо $PATH, при этом bash не жалуется на неизвестную переменную окружения. В Python вы получили бы исключение NameError; на скомпилированном языке код даже не компилировался бы. В bash скрипт просто продолжает выполняться; что может пойти не так?
Решением является параметр -u:
#!/bin/bash
set -eu
export PATH="venv/bin:$PTH" # Typo is deliberate
ls
А теперь bash нашел опечатку:
$ bash bad2.sh
bad2.sh: line 3: PTH: unbound variable
Мы думали, что разобрались с неработающими командами с помощью set -e, но это не решило всех проблем:
#!/bin/bash
set -eu
nonexistentprogram | echo
echo "Success!"
и когда мы запускаем его:
$ bash bad3.sh
bad3.sh: line 3: nonexistentprogram: command not found
Success!
Решение set -o pipefail:
#!/bin/bash
set -euo pipefail
nonexistentprogram | echo
echo "Success!"
Теперь:
$ bash bad3.sh
bad3.sh: line 3: nonexistentprogram: command not found
На данный момент мы имплементировали (большую часть) неофициального "строгого" режима bash [1]. Но и этого все еще недостаточно.
Используя синтаксис $(), вы можете запустить subshell (подоболочку):
#!/bin/bash
set -euo pipefail
export VAR=$(echo hello | nonexistentprogram)
echo "Success!"
Когда мы ее запустим:
$ bash bad4.sh
bad4.sh: line 3: nonexistentprogram: command not found
Success!
Что происходит? Ошибки в подоболочках не воспринимаются, если они являются частью аргументов команды. Это означает, что ошибка в подоболочке просто отбрасывается.
Единственное исключение — это непосредственная установка переменной, поэтому нам нужно написать код следующим образом:
#!/bin/bash
set -euo pipefail
VAR=$(echo hello | nonexistentprogram)
export VAR
echo "Success!"
Теперь наша программа работает правильно:
$ bash good4.sh
good4.sh: line 3: nonexistentprogram: command not found
Возможно, это достаточная демонстрация плохого поведения bash, но далеко не полная.
Каковы могут быть причины, по которым вы все равно захотите использовать shell-скрипты?
Практически каждая вычислительная среда Unix имеет базовую оболочку (shell). Поэтому, если вы пишете какие-то скрипты для упаковки или запуска, возникает соблазн использовать инструмент, который уже там присутствует.
Дело в том, если вы упаковываете Python-приложение, то практически наверняка в среде разработки, CI и среде выполнения будет установлен Python. Так почему бы не использовать язык программирования, который по умолчанию обрабатывает ошибки?
По большому счету, практически каждый язык программирования с достаточно большой пользовательской базой содержит какую-то скрипт-ориентированную библиотеку или идиомы. В Rust, например, есть xshell [2], а также другие библиотеки. Так что в большинстве случаев вы можете использовать свой язык программирования вместо shell-скрипта.
В теории, если вы знаете, что делаете, сохраняете концентрацию и не забываете о бойлерплейте, то можете писать правильные shell-скрипты, даже довольно сложные. А также написать юнит-тесты.
На практике:
Вы, вероятно, работаете не один; вряд ли каждый в вашей команде обладает соответствующим опытом.
Любой человек устает, отвлекается и допускает ошибки.
Почти в каждом сложном shell-скрипте, который я видел, отсутствовал вызов set -euo pipefail, и добавить его постфактум довольно сложно (обычно невозможно).
Не помню, чтобы я когда-либо видел автоматизированный тест для shell-скрипта. Наверняка они существуют, но встречаются довольно редко.
Если вы пишете shell-программы, shellcheck [3] — очень полезный способ поиска ошибок. К сожалению, его одного недостаточно.
Рассмотрим следующую программу:
#!/bin/bash
echo "$(nonexistentprogram | grep foo)"
export VAR="$(nonexistentprogram | grep bar)"
cp x /nosuchdirectory/
echo "$VAR $UNKNOWN_VAR"
echo "success!"
Если мы запустим эту программу, она выдаст "success!", несмотря на то, что у нее 4 отдельные проблемы (как минимум):
$ bash bad6.sh
bad6.sh: line 2: nonexistentprogram: command not found
bad6.sh: line 3: nonexistentprogram: command not found
cp: cannot stat 'x': No such file or directory
success!
Как работает shellcheck? Он выявляет некоторые проблемы... но не все:
Если вы запустите shellcheck, он укажет на наличие неполадок в export.
Если вы запустите shellcheck -o all, чтобы запустить все проверки, он также укажет на проблему с echo "$(nonexistentprogram ...)". Это при условии, что вы используете версию v0.8, которая была выпущена в ноябре 2021 года. Более ранние версии не имели такой проверки, поэтому любой дистрибутив Linux, предшествующий этой версии, выдаст вам shellcheck, который не обнаружит эту проблему.
В нем не предлагается set -euo pipefail.
Если вы полагаетесь на shellcheck, я настоятельно рекомендую обновиться и убедиться, что вы запускаете его с параметром -o all.
В определенных ситуациях shell-скрипты вполне уместны:
Для разовых скриптов, которые вы администрируете вручную; здесь можно обойтись методами попроще.
Иногда у вас действительно нет гарантий, что доступен другой язык программирования, и вам нужно использовать shell, чтобы все заработало.
В достаточно простых случаях, когда требуется выполнить несколько команд последовательно, без подоболочек, условной логики или циклов, достаточно использовать set -euo pipefail (и обязательно используйте shellcheck -o all).
Как только вы обнаружите, что дополнительно делаете что-то сверх этого, начните использовать менее подверженный ошибкам язык программирования. А учитывая, что большая часть программного обеспечения имеет тенденцию со временем расти, лучше всего начинать с чего-то менее ломкого.
Материал подготовлен для будущих учащихся на курсе [4] "Administrator Linux. Advanced". Всех желающих приглашаем на бесплатные demo-заняти:
«Puppet — система контроля конфигураций». На занятии будет дан обзор архитектуры puppet, его основных инструментов и методов их использования, на практике будет разобран вопрос установки, первоначальной настройки сервера и клиента, а также пример использования: настройка служб, конфигурационных файлов, установка пакетов. Регистрация [5]
«Введение в Docker». На занятии мы рассмотрим основы контейнеризации и ее отличие от виртуализации, плавно перейдем к рассмотрению самого популярного на данный момент инструмента контейнеризации Docker — узнаем, из каких основных компонентов и сущностей он состоит, и как они взаимодействуют между собой. Регистрация [6]
Автор:
rikki_tikki
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/373415
Ссылки в тексте:
[1] неофициального "строгого" режима bash: http://redsymbol.net/articles/unofficial-bash-strict-mode/
[2] xshell: https://docs.rs/xshell/
[3] shellcheck: https://www.shellcheck.net/
[4] курсе: https://otus.pw/WFiK/
[5] Регистрация: https://otus.pw/9oH1/
[6] Регистрация: https://otus.pw/aqgH/
[7] Источник: https://habr.com/ru/post/657841/?utm_source=habrahabr&utm_medium=rss&utm_campaign=657841
Нажмите здесь для печати.