Bash, он же возрождённый shell, является по-прежнему одним из самых популярных командных процессоров и интерпретаторов сценариев. Как бы его ненавидели и не пытались заменить, всё равно он присутствует вокруг нас и никуда не собирается исчезать. Если вам приходится писать bash скрипты или вы только планируете этим заняться, данная статья написана для вас.
Статья несет исключительно рекомендательный характер и затрагивает в первую очередь bash, но также будет полезна и для работы с совместимыми оболочками, такими как: sh, ash, csh, ksh и tcsh.
На данный момент, тема bash скриптинга не менее актуальна чем 10 или 20 лет назад. Хотя большинство дистрибутивов Linux перешли на Systemd или аналоги, а System V с скриптами для запуска служб ушел на пенсию, многое по прежнему реализовано при помощи скриптов и многие из них - это shell скрипты. В ключевых и популярных дистрибутивах, bash является оболочкой по умолчанию, и это неспроста, при помощи него легко автоматизировать рутину. С появлением docker, а после и kubernetes, тема bash скриптов стала только актуальнее, количество всевозможных docker-entrypoint.sh
, Job, CronJob и initContainers растет, а реализуются они чаще всего при помощи bash. Инструкция RUN
в Dockerfile
, вовсе внесла огромный вклад в мировой запас shell строк.
Это несложно, но необходимо знать некоторые основы bash скриптинга. Поехали!
Текстовый редактор
Начнем с выбора среды разработки, именно она позволяет объединять различные аспекты написания программы, повышая продуктивность за счет объединения общих действий по написанию программного обеспечения в одном приложении.
Я в своей практике использовал разные редакторы для работы с shell скриптами, приведенный список не является конечным, и перечислю только те, которые запомнились больше всего. Разделим на три условные категории:
Консольные текстовые редакторы. Vim, Emacs и Nano - классическая троица, сейчас уже редко кто использует на рабочих станциях как основной инструмент, но vi и nano незаменимы для быстрого редактирования файлов в удаленных ssh сессиях. Если вы еще не работали с ним, рекомендую освоить такие вещи как поиск, замена и форматирование, хотя бы в nano.
Графические текстовые редакторы. Mousepad, Gedit, Notepad++ и т.п. Легковесные редакторы, с подсветкой синтаксиса, автозаменой и прочим, что уже есть в консольных редакторах, но они всё еще не являются полноценной интегрированной средой разработки.
IDE. Geany, Atom, IntelliJ IDEA, Sublime Text и Visual Studio Code - это уже полноценные и расширяемые среды разработки. Долгие годы я пользовался Geany и пробовал все перечисленные варианты, но только с появлением VSCode мне удалось сменить основную IDE для большинства задач.
ℹ️ Половина статьи затрагивает конфигурацию параметров и расширения для Visual Studio Code. Хотя, эта IDE может быть не в вашем вкусе, но информация приведенная в статье будет полезна в академических целях, а полученные знания, вы можете адаптировать под свое любимое окружение.
Альтернативные редакторы
Существует как минимум три альтернативных среды разработки для написания bash скриптов:
Специализированная IDE BashEclipse основанная на Eclipse.
В IntelliJ IDEA можно добиться расширенной поддержки bash скриптинга путем установки расширений Shell Script, ShellCheck и BashSupport.
Bash Kernel для Jupyter Notebook.
Настройка окружения
Сперва следует настроить редактор так, чтобы он помогал нам писать скрипты в едином стиле и исправлял за нас небольшие огрехи.
Ширина строк кода
Начнем мы с того, что выведем на экран вертикальные линии для отображения столбцов 72, 80 и 132 символов, данная длина является «стандартным» пределом для ширины кода. И мы будем стараться при написании скриптов умещаться в границу 80 столбцов, 72 столбец будет нашим желанным ориентиром, а 132 столбец пометим как край документа, применимый в исключительных ситуациях, за ним же следует пропасть.
Добавим нижеприведенную конфигурацию в файл настроек settings.json
для Visual Studio Code.
Как найти settings.json
Откройте Visual Studio Code
Нажмите F1, чтобы открыть командную панель
Введите в открывшуюся панель «open settings»
Вам представлены два варианта, выберите «Open Settings (JSON)»
{
"[shellscript]": { // настройки применимые только для shellscript файлов
"editor.rulers": [ // вертикальные лини подсветки столбцов 72, 80, 132
{ "column": 72, "color": "#1e751633", },
{ "column": 80, "color": "#c2790b99", },
{ "column": 132, "color": "#a10d2d99" }
],
"editor.minimap.maxColumn": 132, // ширина миникарты
"editor.wordWrap": "off", // запрещаем перенос строк
},
// ... прочие настройки
}
Краткий ответ, почему именно 72, 80 и 132 символа
Вы можете поблагодарить перфокарту IBM 1928 года за этот предел - в ней было 80 столбцов. Почему 80? Дело в том, что типичный шаг пишущей машинки 10-12 символов на дюйм, а это приводит к документам шириной от 72 до 90 символов, в зависимости от размера полей. После этого ранние телетайпы, а затем видео-терминалы использовали 80 столбцов, а затем 132 столбца в качестве стандартной ширины. Сейчас же, к примеру, пропорции окна эмулятора терминала по умолчанию остаются равными 80x24.
Но ведь сейчас же не 1928 год! Да, но эргономика чтения файлов в 80 столбцов гораздо выше, а при сравнении двух файлов мы используем в два раза большую ширину (бывают еще diff-ы трех состояний). Все еще используются удаленные параллельные терминалы в гипервизорах и KVM-свитчи в серверных, и порой приходится что-то быстро поправить через них, находясь в сессии с разрешением 1024x768, а может и меньшим.
Возможно, вы владелец 4K+ дисплея и думаете, что вас это не касается. Но подумайте о других, кто будет использовать ваши скрипты, если конечно они публикуются за рамками вашего localhost.
Отступы и окончание строк
Одной из проблем, которую я встретил 15 лет назад, когда только знакомился с bash и наблюдаю по сей день, это CRLF (\r\n
или 0x0D0A
) в файлах сценариев. Источником проблемы, чаще всего является копипаста bash скриптов в windows системах, но также это может быть и просто по невнимательности. Давайте настроим завершение сток при помощи LF.
Помимо типа окончания строк, также включим добавление пустой строки в конец файла и настроим отступы и их визуализацию.
{
"[shellscript]": { // настройки применимые только для shellscript
"files.eol": "\n", // явно зададим LF формат EOL
"files.insertFinalNewline": true, // завершаем все файлы новой строкой
"files.trimFinalNewlines": true, // удалим лишние новые строки в конце файла
"files.trimTrailingWhitespace": true, // удалим лишние пробелы в конце строк
"editor.renderWhitespace": "boundary", // отобразим два и более пробелов
"editor.insertSpaces": true, // отступы делаем пробелами
"editor.tabSize": 2, // размер отступа в два пробела
},
// ... прочие настройки
}
Чем страшен CRLF?
Возврат каретки - это управляющий символ. Когда вы печатаете его на терминале, вместо отображения глифа терминал выполняет некоторый специальный эффект. Для возврата каретки специальный эффект заключается в перемещении курсора в начало текущей строки. Таким образом, если вы напечатаете строку, содержащую возврат каретки посередине, то в результате вторая половина будет записана поверх первой.
Давайте рассмотрим на примере, у нас есть простейший скрипт:
#!/usr/bin/env bash
set -eu
printf '%s ' "Hi ${USER:-John Doe}! Today is"
LANG=en date
Сохраним его в файл test-eol.sh
, сделаем его исполняемым chmod +x ./test-eol.sh
и проверим работу:
# ./test-eol.sh
Hi woozymasta! Today is Mon Oct 18 00:48:13 MSK 2021
Всё хорошо, давайте заменим LF на CRLF, можно воспользоваться командой sed $'s/$/\r/' -i test-eol.sh
и запустим сценарий еще раз:
# ./test-eol.sh
/usr/bin/env: 'bash\r': No such file or directory
Скрипт упал с ошибкой о том, что файла bash\r
не существует, утилита env приняла на вход строку как есть. И это хорошо, что скрипт упал, ведь могло произойти что-то более непредвиденное. Давайте обойдем использование shebang, передав путь к скрипту как аргумент для bash:
# bash ./test-eol.sh
./bash-eol.sh: line 2: $'\r': command not found
: invalid optionine 3: set: -
set: usage: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
./bash-eol.sh: line 4: $'\r': command not found
./bash-eol.sh: line 6: $'date\r': command not found
Как видно, теперь были обработаны все инструкции скрипта. Правда set -e не смог выполниться, и каждая команда сценария, смогла выполниться с ненулевым кодом возврата.
В этом примере, ничего страшного не произошло, но, что если у вас был бы такой скрипт:
#!/usr/bin/env bash
set -eu
printf '%s ' "Hi ${USER:-John Doe}! Today is"
LANG=en date || \
{ rm -rf / --no-preserve-root; echo "you will not pass"; }; echo Done
❗ Осторожно! Для проверки этого скрипта, всё же лучше замените
rm -rf / --no-preserve-root
, к примеру наtouch test
Зачем нужна пустая строка в конце файла?
Речь идет не о добавлении дополнительной строки в конец файла, а о том, чтобы не удалять новую строку, которая должна быть там.
Текстовый файл в Unix состоит из серии строк, каждая из которых заканчивается символом новой строки \n
. Таким образом, файл, который не является пустым и не заканчивается новой строкой, не является текстовым файлом.
Утилиты, которые должны работать с текстовыми файлами, могут не справиться с файлами, которые не заканчиваются символом новой строки. Исторические утилиты Unix могут, например, игнорировать текст после последней новой строки. Утилиты GNU придерживаются политики приличного поведения с нетекстовыми файлами, как и большинство других современных утилит, но вы все равно можете столкнуться со странным поведением с файлами, в которых отсутствует последняя новая строка.
И я бы предложил использовать добавление новой строки по умолчанию, во все редактируемые файлы, естественно если на то нет ограничений у формата.
Автосохранение
Данный момент выделен отдельно неспроста, редактировать shell сценарии в процессе их работы крайне нежелательно. Всё потому, что файл читается построчно и внесенные правки во время работы сценария могут вызвать непредвиденное поведение.
Просто помните об этом, и не включайте автосохранение при написании bash скриптов. К сожалению параметр files.autoSave
не поддерживается для выборочных типов файлов, а устанавливается глобально на всё окружение.
Проверим на практике редактирование уже исполняющегося скрипта
Ситуации могут быть разные, к примеру, у вас есть долгоиграющий скрипт, и вы, в процессе его работы решили, всего лишь добавить еще одно отладочное сообщение в теле цикла. Скорее всего вас ждут проблемы на выходе из цикла.
А теперь рассмотрим простой доказательный пример, создадим скрипт test.sh
работающий две секунды:
#!/usr/bin/env bash
sleep 1s
echo one
sleep 1s
echo two
Запустим скрипт и по итогу выполнения напечатаем код выхода, объединим это в группу и запустим в отдельном потоке, а пока он отработает половину отведенного ему времени, допишем в него еще одну команду exit 42 :
{ ./test.sh && echo $?; } & sleep 1s; echo 'exit 42' >> ./test.sh; wait
ℹ️ Если однострочники у вас вызывают некоторое волнение, воспользуйтесь сервисом explainshell.com, он поможет на первых порах разбирать такие конструкции.
И вот итог:
[1] 1208831
one
two
[1]+ Выход 42 { ./test.sh && echo $?; }
Но допустим автосохранение отключать нельзя, или вы сами на автомате нажали Ctrl+S, можно как-то предостеречь это поведение?
Вариантов на самом деле много, начиная созданием копий файла, но я бы предложил отправить скрипт в трубу:
cat ./test.sh | bash -s - "${args[@]}"
Но и здесь имеется ограничение, это размер буфера, равный 65536 байтам, с скриптом вес которого превышает размер буфера, этот трюк уже не пройдет как ожидалось.
Пожалуй это все параметры для редактора, которые хотелось осветить. Для удобства настройки, все параметры которые были внесли в settings.json приведены ниже:
Все параметры в settings.json для shellscript
{
"files.autoSave": "off",
"[shellscript]": {
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"editor.renderWhitespace": "boundary",
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.tabCompletion": "on",
"editor.wordWrap": "off",
"editor.rulers": [
{
"column": 72,
"color": "#1e751633",
},
{
"column": 80,
"color": "#c2790b99",
},
{
"column": 132,
"color": "#a10d2d99"
}
],
"editor.minimap.maxColumn": 132,
}
}
Утилиты и расширения
Всё обилие возможностей и расширенное погружение в написание bash сценариев, открывается при использовании дополнительных утилит, таких как: линтер, отладчик, форматер, языковой сервер и т.п. Сами по себе утилиты хоть и решают свои функциональные задачи, только с интеграцией в IDE они по настоящему раскрывают свою мощь.
ShellCheck
ShellCheck - это инструмент который дает предупреждения и предложения для сценариев bash и sh. Незаменимая вещь, которую следует использовать повсеместно для написания скриптов и встраивать в CI пайплайны. Поможет писать сценарии более корректно и надежно, укажет на типичные проблемы синтаксиса и семантические проблемы, а также уведомит о тонкостях и возможных подводных камнях в разных конструкциях.
Рекомендуется использовать последний релиз приложения.
Для проверки сценария достаточно выполнить:
shellcheck /path/to/script.sh
Пример результата работы shellcheck
In /path/to/script.sh line 5:
echo $none
^---^ SC2154: none is referenced but not assigned.
^---^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
echo "$none"
In /path/to/script.sh line 6:
. ./test
^----^ SC1091: Not following: ./test was not specified as input (see shellcheck -x).
Но гораздо нагляднее, будет видеть все предупреждения и подсказки в самой IDE. Для этого установим расширение ShellCheck:
ext install timonwong.shellcheck
BASH Debugger
BASH Debugger - это внешний отладчик для bash, который следует синтаксису команды gdb.
К сожалению в большинстве дистрибутивов пакет или отсутствует, или имеет очень старую версию, по этому соберем проект из исходников. Скачаем последнюю версию или клонируем с зеркала на github и собираем:
tar xf bashdb-5.0-1.1.2.tar.gz
cd bashdb-5.0-1.1.2/
./configure
make
sudo make install
# можно взять один бинарь и обойтись без make install
# если работать c bashdb будем только из vscode
# cp bashdb ~/.local/bin/
Теперь для запуска отладки скрипта выполним команду:
bash --debugger -- /path/to/script.sh
# или
bashdb /path/to/script.sh
Пример результата работы bashdb
bash debugger, bashdb, release 5.0-1.1.2
Copyright 2002-2004, 2006-2012, 2014, 2016-2019 Rocky Bernstein
This is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
(/path/to/script.sh:5):
5: echo $none
bashdb<0> backtrace
->0 in file `./bash-eol.sh' at line 5
##1 main("/usr/share/bashdb/bashdb-main.inc") called from file `/path/to/script.sh' at line 0
bashdb<1> debug
Debugging new script with /usr/bin/bash --init-file /tmp/bashdb_profile_1067091 --debugger echo
/usr/bin/echo: /usr/bin/echo: не удалось запустить двоичный файл
bashdb<2> continue
/path/to/script.sh: строка 6: ./test: Нет такого файла или каталога
(standard_in) 1: syntax error
hi
Done
Но это не так удобно, как расставлять точки останова в IDE, по этому установим расширение Bash-debug:
⚠️ Внимание! Для работы требует наличия bashdb в системе
ext install rogalmic.bash-debug
Shell Format
Shfmt - утилита для форматирования shell сценариев. Установим последний релиз и попробуем на практике:
# Форматировать скрипт с настройками по умолчанию, вывод направить файл
shfmt /path/to/script.sh > /path/to/script-formated.sh
# Перезаписать файл, использовать отступ в два пробела
shfmt -w -i 2 ~/desktop/bash-eol.sh
Очередь расширения, установим Shell Format:
ext install foxundermoon.shell-format
Нам стало доступно форматирование файлов shellscript
, и им уже можно воспользоваться. Для этого вызовем палитру команд, нажав F1, введем в поле «format document» и выберем этот же пункт.
Помимо этого, вы можете настроить автоматическое форматирование документов, при сохранении файла, параметр editor.formatOnSave
.
ℹ️ Увы расширение не позволяет форматировать выделенный блок кода, в связи с этим недоступен параметр
editor.formatOnPaste
, позволяющий форматировать код вставляемый из буфера обмена.
Bash Language Server
Bash Language Server - языковой сервер для интеграции в множество различных IDE. Установка языкового сервера приносит нам поведение среды разработки, как у больших языков программирования, такие возможности как: поиск ссылок, переход к объявлению, автодополнение, документация и т.п.
Для VSCode достаточно установить расширение Bash IDE:
ext install mads-hartmann.bash-ide-vscode
ℹ️ Расширение также поддерживает интеграцию с explainshell, но для этого вам понадобится держать запущенным сервер explainshell, а на выходе вы не получите всей той магии, что доступна на сайте explainshell.com. В связи с этим интеграцию считаю сомнительной, и у себя не использую.
Shell Completion
Работая с bash как оболочкой, во многих моментах помогает автодополнение по TAB, так вот для VSCode есть возможность дополнять аргументы для команд, реализуется это при помощи расширения Shell Completion. Давайте проверим:
ext install tetradresearch.vscode-h2o
Manpages
Самая актуальные и корректные руководства к утилитам, зачастую находится локально в man, почему бы не читать их напрямую в среде разработки. Manpages поможет нам в этом, установим его:
ext install meronz.manpages
Использовать расширение просто, выделяем в теле скрипта имя интересной нам команды и просим показать man через палитру команд или в контекстном меню.
ShellMan
Shellman - наверное единственная совместимая с ShellCheck коллекция сниппетов для bash. Будет полезно как новичкам, для более быстрого знакомства с скриптами, так и бывалым разработчикам позволит сэкономить время на написание рутинных конструкций. В магазине расширений доступно около десятка расширений с снипетами для shell скриптов наряду с Shellman, при желании вы можете комбинировать их.
Установка расширения:
ext install Remisa.shellman
Подробно ознакомится с возможностями и советами как пользоваться ShellMan вы можете в книге shellman-ebook.
Code Runner
Code Runner - расширение, позволяющее выполнять произвольный блок кода в самой IDE, для этого достаточно выделить необходимые строки и нажать CTRL+ALT+N, или вызвать данную функцию из контекстного меню, или палитры команд. Это заметно ускорит процесс написания скриптов.
ext install formulahendry.code-runner
Демонстрация работы (GIF 5МБ)
Hadolint
Hadolint - это, пожалуй лучший линтер для Dockerfile
. Почему он оказался в этом списке? Всё довольно просто, в Dockerfile
имеется инструкция RUN
в которой размещается shell скрипт, а Hadolint помимо общей проверки синтаксиса файла, также использует ShellCheck для проверки этих скриптов.
Скачаем последнюю версию приложения с страницы релизов. Запустим утилиту, передав путь к Dockerfile
как аргумент.
hadolint ./Dockerfile
И установим расширение Hadolint в VSCode:
⚠️ Внимание! Для работы требует наличия hadolint в системе
ext install exiasr.hadolint
И как бонус, для подсветки синтаксиса shell скриптов в RUN секции Dockerfile, можно воспользоваться расширением Better Dockerfile Syntax.
Txt Syntax
Еще одно вспомогательное расширение Txt Syntax, напрямую не влияющее на bash скрипты, но позволяет выделить текстовые файлы (.txt, .out .tmp, .log, .ini, .cfg ...) и предоставить общие служебные инструменты для текстовых документов. Shell сценарии часто опираются на всевозможные текстовые файлы, и будет полезно упростить работу с ними в IDE.
ext install xshrim.txt-syntax
ℹ️ Данное расширение помогает работать расширению manpages, а именно складывать и раскладывать заголовки в документах справки.
Better Shell Syntax
И в завершении списка, расширим подсветку синтаксиса. По умолчанию подсветка не настолько хороша как могла быть, и расширение Better Shell Syntax пытается исправить это, позволяя вашей теме лучше раскрашивать код.
ext install jeff-hykin.better-shellscript-syntax
ℹ️ Расширение не будет работать с стандартной темой (не будет эффекта), но всё будет хорошо в таких темах как: Material Theme, Gruvbox, XD Theme и подобных.
При этом может не очень хорошо работать с вашей любимой, нестандартной темой оформления, перед использованием, проверьте всё ли вас устраивает.
Примеры
Вот мы и закончили с обзором утилит и расширений. Последние два (Txt Syntax и Better Shell Syntax) несут больше косметический характер, и их можно смело пропустить, чего не могу сказать про весь оставшийся список, рекомендую хотя бы попробовать их на практике.
Для удобства установки, все расширения собраны в один пакет Shell script IDE, правда бинарные зависимости (bashdb и hadolint) придется устанавливать самостоятельно.
ext install woozy-masta.shell-script-ide
Отладка
Хорошо когда настроенная IDE есть под рукой, но не всегда бывает так, к примеру мы работаем на удаленном сервере или в контейнере. По этому затронем тему настройки окружения для отладки и немного коснемся её самой.
Когда что-то идет не по плану, вам нужно определить, что именно вызывает сбой сценария. Bash предоставляет возможность для отладки, это запуск подоболочки с параметром -x
, который запускает весь сценарий в режиме отладки. Следы каждой команды плюс ее аргументы выводятся на стандартный вывод после того, как команды были развернуты, но до их выполнения.
Еще немного про ключи для отладки
Параметр отладки может быть установлен в произвольном месте в теле скрипта. Для отладки определенного блока кода, установим перед кодомset -x
, а для выхода из отладки при достижении конца отлаживаемого блока, обратим параметр вызвав set +x
.
Минус используется для активации опций оболочки, а плюс для деактивации. Пусть это вас не смущает.
Параметры которые вам скорее всего понадобятся для отладки:
|
Отключить получение имени файла с использованием метасимволов (подстановка). |
|
Печатает строки ввода оболочки по мере их чтения. Листинг скрипта будет предварительно выводиться на экран перед командами. |
|
Печатает трассировку команд перед выполнением команды. |
|
Не исполнять сценарий, а только проверить на наличие синтаксических ошибок. Проверка будет выполнена только для грубых ошибок, надежнее использовать shellchek. |
Также длинные параметры следующие за set -o
могут быть переданы через переменную SHELLOPTS
или используя родную для bash команду shopt
.
В shopt включение или отключение опций происходит при помощи флагов:
-s (set)
- установить опцию;-u (unset)
- отключить опцию.
Для того что бы отобразить текущие настройки параметров, выполните set -o
или shopt
Для экспериментов, давайте создадим простой скрипт.
Скрипт test.sh
#!/usr/bin/env bash
set -eu
function print-msg () {
printf '%b%-20s%b' "${colors[${1:-0}]}" "${@:2}" "${colors[0]}"
}
function random-color-echo() {
print-msg $((1 + RANDOM % $((4 - 1)))) "${*:-}"
}
function msg () {
random-color-echo "Hi ${*:-}!"
}
colors=(
"$(tput sgr0)" # reset
"$(tput setaf 1)" # red
"$(tput setaf 2)" # green
"$(tput setaf 3)" # yellow
"$(tput setaf 4)" # blue
)
for item in {"Bob","Alice"}; do
echo "$({ msg "$item"; ( date '+%s%N' ); } & wait)"
done
echo 'Done'
И выполним его при помощи bash -x ./test.sh
или добавив set -x
в начало скрипта:
Замечательно, теперь мы видим как работает наш скрипт. Стоит только пояснить, что означает +
, во первых, как вы догадались, за ним следует трассировка команды из скрипта, а вот количество знаков меняется и оно обозначает несколько уровней косвенного обращения.
Для небольших блоков логики этого зачастую достаточно, но, что если хочется большего? И первое, что мы можем сделать, это добавить необходимую информацию в параметр PS4
:
# Levels of indirection and time
PS4='+\011\[\e[3;34m\]\t\[\e[0m\]'
# User ID [Effective user ID]: Groups of user is a member
PS4+=' \[\e[0;35m\]$UID[$EUID]:$GROUPS\[\e[0m\] '
# Shell level and subshell
PS4+='\011\[\e[1;31m\]L$SHLVL:S$BASH_SUBSHELL\[\e[0m\]'
# Source file
PS4+=' \[\e[1;33m\]${BASH_SOURCE:-$0}\[\e[0m\]'
# Line number
PS4+='\[\e[0;36m\]#:${LINENO}\[\e[0m\]'
# Function name
PS4+='\011\[\e[1;32m\]${FUNCNAME[0]:+${FUNCNAME[0]}(): }\[\e[0m\]'
# Executed command
PS4+='\n# '
export PS4
ℹ️ Объявить
PS4
вы можете в своем~/.bashrc
и он будет с вами постоянно, или определить свой формат отладки непосредственно в теле самого скрипта, или временно экспортировать изменения на время жизни оболочки bash.
О назначении параметров: PS0, PS1, PS2, PS3 и PS4
PS0
- Значение этого параметра раскрывается и отображается интерактивными оболочками после прочтения команды и до ее выполнения. Т.е. это будет напечатано перед исполнением каждой команды, по умолчанию не установлено.PS1
- Значение этого параметра раскрывается и используется в качестве основной строки приглашения. Это ваше стандартное приветствиеuser@host:~
PS2
- Значение этого параметра раскрывается, как и в случае сPS1
, и используется в качестве дополнительной строки приглашения.PS3
- Значение этого параметра используется в качестве подсказки для командыselect
.PS4
- Значение этого параметра расширяется, как в случае с PS1, и значение печатается перед отображением каждой команды bash во время трассировки выполнения. Первый символ расширенного значения PS4 при необходимости повторяется несколько раз, чтобы указать несколько уровней косвенного обращения. По умолчанию+
И снова запустив скрипт, мы увидим уже немного другой результат:
Давайте разберем этот пример, а в дальнейшем вы сами сможете реализовать удобный вывод отладочной информации под ваши нужды.
+
- Первый символ, отображает уровни косвенного обращения к командам, эта часть осталась как в оригинальномPS4
.\t
- Текущее время, полезно для изучения тайминга команд, может быть заменено к примеру командойdate '+%x %X:%N %z'
для более подробного информирования, включая отображение наносекунд.$UID[$EUID]:$GROUPS
- Выведем ID и эффективный ID пользователя, перечисляем группы, членом которых является текущий пользователь. Это будет полезно для скриптов выполняющих действия от разных пользователей.-
L$SHLVL:S$BASH_SUBSHELL
- Отображения уровня оболочки, и уровня вложенной подоболочкой. Когда вы запускаете команду в оболочке, она запускается на уровне, называемом уровнем оболочки. Внутри оболочки вы можете открыть другую оболочку, которая делает её подоболочкой, или оболочку, которая её открыла.Уровень оболочки
SHLVL
поможет понять насколько глубоко вы находитесь в дочерних сессиях, ведь у каждой последующей оболочки могут быть добавлены или переопределены важные вам параметры.Уровень подоболочки
BASH_SUBSHELL
позволяет отслеживать все дочерние вызванные оболочки, к примеру, дочерняя оболочка не может вернуть переменную в родительскую оболочку.
${BASH_SOURCE:-$0}
- Имя исполняемого файла или функции.#:${LINENO}
- Номер трассируемой строки.${FUNCNAME[0]:+${FUNCNAME[0]}(): }
- Имя функции в рамках которой происходит исполнение.
ℹ️ Подробную информацию о параметрах вы всегда найдете в
man bash
разделах PROMPTING и PARAMETERS/Shell Variables
Если вы обрабатываете вывод скрипта на лету или объем отладочного лога очень велик, было бы удобно направить трассировку в отдельный файл. Для этих целей существует параметр BASH_XTRACEFD
, он позволяет указать номер файлового дескриптора для вывода сообщений трассировки.
Для этого мы создадим ссылку для файлового дескриптора с номером 3 на файл debug_$0.log
где $0
это имя bash сценария, а переменной BASH_XTRACEFD
передадим номер нашего нового дескриптора.
set -x
exec 3> "debug_$0.log"
BASH_XTRACEFD="3"
Также имеется возможность перенаправить вывод не в файл, а в утилиту, к примеру отправив сообщения утилите logger
мы сможем обратится к журналу при помощи командыjournalctl -t test.sh
.
set -x
exec 3> >(logger -t "$0")
BASH_XTRACEFD="3"
Теперь мы знаем как можно сделать отладку для всего сценария, или только для отдельной его части. Существует ли возможность принудительно исключить из отладки одну функцию? Да, и для этого достаточно в начало функции добавить такую конструкцию:
function some () {
{ local -; set +x; } 2>/dev/null
echo 'Do some stuff'
}
На тот случай если стандартной трассировки bash вам недостаточно, нужно получить больше информации о работе скрипта, выполнить более тонкое профилирование работы или разобраться с зависаниями, обратитесь к таким системным инструментам как strace
или в очень специфичной ситуации gdb
(надеюсь с вами этого не произойдет)
Пример запуска отладки скрипта при помощи strace
:
strace -C -f bash -x ./test.sh
Благодарю за ваше время и внимание, эффективного bash скриптинга вам!
Присоединяйтесь в телеграмм канал, где я периодически публикую заметки на тему DevOps, SRE и архитектурных решений.
Комментарии (11)
DirectoriX
24.10.2021 12:11+1Внимение! Для работы требует наличия ShellCheck в системе
ShellCheck-расширение при установке вполне подтягивает свой собственный бинарник, проверено на Windows10, Debian 10, Fedora 34.
То же самое и shell-format.WoozyMasta Автор
24.10.2021 13:30+1Спасибо за важное замечание.
И в правду, начиная с версии v0.10.0 расширения ShellCheck бинарники встроены (уже как год прошел). Shell-format раньше имел проблемы со скачиванием, сейчас проверил, всё работает.
Исправил это в статье и описании пакета с расширениями.
phikus
24.10.2021 23:51+2Благодарю, вот уж не думал что в 2021 смогу узнать что-то новое про отладку шелла, но вам это удалось :)
Kirikekeks
26.10.2021 23:06-2Каша из топора. Оверхед зашкаливает. Баш и вот это выше вещи антогонистичные и взаимоисключающие. А давайте у ложки наточим ручку, что бы резать этим краем, и зазубрим с другой стороны, что бы пилить, и дырочку просверлим под крючек, что бы блеснить. А есть и палочками можно, да и без палочек получится. Таким методом что угодно можно маштабировать в идиотизм.
WoozyMasta Автор
26.10.2021 23:23Простите, я не понял, что вы хотели этим сказать. Как bash может быть соперником
strace
,PS4
или IDE? Вы не путаете тёплое с мягким?В чем вы увидели дополнительные накладные расходы? Это же всего лишь рекомендации о настройке персонального рабочего окружения, для написания сценариев.
Сами же сценарии от этого сложнее или медленнее не станут, но их будет проще писать и отлаживать на этапе разработки.
Kirikekeks
27.10.2021 13:25Первый коммент, первая ссылка на отличные рекомендации, в первых строках гугель рекомендует не "путаете тёплое с мягким?"
Some guidelines:
If you’re mostly calling other utilities and are doing relatively little data manipulation, shell is an acceptable choice for the task.
If performance matters, use something other than shell.
If you are writing a script that is more than 100 lines long, or that uses non-straightforward control flow logic, you should rewrite it in a more structured language now. Bear in mind that scripts grow. Rewrite your script early to avoid a more time-consuming rewrite at a later date.
WoozyMasta Автор
27.10.2021 23:02У меня складывается впечатление, что вы не читая статьи написали комментарий. Надеюсь я ошибаюсь, и просто не улавливаю всей глубины вашей мысли.
В статье, я не пропагандирую использовать bash, не привожу никаких гайдлайнов по логической составляющей скриптов и не призываю писать в каком то стиле. Я рассказываю как настроить комфортную среду, в которой вы пишете bash скрипты, а уж по какой причине и для чего вы их пишете, это дело ваше, и я вас не принуждал к этому.
x1shn1k
03.11.2021 20:52Автор - браво!
У меня только shellcheck установлен, скрипты пишу не часто.
Но теперь знаю как "прокачать" VSC в случае чего.
MzMz
Мне понравился толковый styleguide для shell-скриптов от Google
Samamy
Есть статья на хабре https://habr.com/ru/post/413155/