Как использовать set и pipefail в сценариях Bash в Linux

Опубликовано: 2022-06-25
Терминал Linux на экране ноутбука на синем фоне.
Фатмавати Ачмад Заэнури/Shutterstock.com

Команды Linux set и pipefail определяют, что произойдет, если в сценарии Bash произойдет сбой. Есть о чем подумать, чем остановиться или продолжаться.

СВЯЗАННЫЕ С: Руководство для начинающих по сценариям оболочки: основы

Сценарии Bash и условия ошибок

Сценарии оболочки Bash великолепны. Они быстро пишутся и не требуют компиляции. Любое повторяющееся или многоэтапное действие, которое вам нужно выполнить, можно завернуть в удобный сценарий. А поскольку сценарии могут вызывать любые стандартные утилиты Linux, вы не ограничены возможностями самого языка оболочки.

Но проблемы могут возникнуть при вызове внешней утилиты или программы. В случае сбоя внешняя утилита закроется и отправит код возврата в оболочку и может даже вывести сообщение об ошибке на терминал. Но ваш скрипт продолжит обработку. Возможно, это не то, что вы хотели. Если ошибка возникает на ранней стадии выполнения скрипта, это может привести к более серьезным проблемам, если остальная часть скрипта будет запущена.

Что такое оболочка Bash и почему она так важна для Linux?
СВЯЗАННЫЕ С : Что такое оболочка Bash и почему она так важна для Linux?

Вы можете проверять код возврата от каждого внешнего процесса по мере их завершения, но это становится затруднительным, когда процессы передаются в другие процессы. Код возврата будет от процесса в конце конвейера, а не от того, который потерпел неудачу в середине. Конечно, ошибки могут возникать и внутри вашего скрипта, например, попытка доступа к неинициализированной переменной.

Команды set и pipefile позволяют решить, что произойдет при возникновении подобных ошибок. Они также позволяют обнаруживать ошибки, даже если они происходят в середине цепочки конвейеров.

Вот как их использовать.

Демонстрация проблемы

Вот тривиальный скрипт Bash. Он выводит на терминал две строки текста. Вы можете запустить этот скрипт, если скопируете текст в редактор и сохраните его как «script-1.sh».

 #!/бин/баш

эхо Это произойдет первым 
эхо Это произойдет второй

Чтобы сделать его исполняемым, вам нужно использовать chmod :

 chmod +x скрипт-1.sh

Вам нужно будет запустить эту команду для каждого скрипта, если вы хотите запустить их на своем компьютере. Запустим скрипт:

 ./скрипт-1.ш 

Запуск простого скрипта без ошибок.

Две строки текста отправляются в окно терминала, как и ожидалось.

Немного изменим скрипт. Мы попросим ls указать сведения о несуществующем файле. Это не удастся. Мы сохранили это как «script-2.sh» и сделали его исполняемым.

 #!/бин/баш

эхо Это произойдет первым
ls воображаемое-имя-файла
эхо Это произойдет второй

Когда мы запускаем этот скрипт, мы видим сообщение об ошибке от ls .

 ./скрипт-2.ш 

Запуск сценария и создание условия отказа.

Несмотря на сбой команды ls , сценарий продолжал выполняться. И хотя при выполнении скрипта произошла ошибка, код возврата из скрипта в оболочку равен нулю, что говорит об успехе. Мы можем проверить это, используя echo и $? переменная, которая содержит последний код возврата, отправленный в оболочку.

 эхо $? 

Проверка кода возврата для последнего выполненного скрипта.

Ноль, о котором сообщается, является кодом возврата из второго эха в сценарии. Таким образом, есть две проблемы с этим сценарием. Во-первых, в сценарии произошла ошибка, но он продолжал работать. Это может привести к другим проблемам, если остальная часть скрипта ожидает или зависит от того, что действие, которое не удалось, на самом деле удалось. А во-вторых, если другому сценарию или процессу необходимо проверить успех или неудачу этого сценария, он получит ложное чтение.

Опция set -e

Параметр set -e (выход) вызывает завершение сценария, если какой-либо из процессов, которые он вызывает, генерирует ненулевой код возврата. Все, что не равно нулю, считается ошибкой.

Добавив опцию set -e в начало скрипта, мы можем изменить его поведение. Это «script-3.sh».

 #!/бин/баш 
установить -е

эхо Это произойдет первым
ls воображаемое-имя-файла
эхо Это произойдет второй

Если мы запустим этот скрипт, мы увидим эффект set -e .

 ./скрипт-3.ш
 эхо $? 

Завершение скрипта при возникновении ошибки и правильная установка кода возврата.

Сценарий останавливается, и код возврата, отправляемый в оболочку, имеет ненулевое значение.

Устранение неполадок в трубах

Трубопровод усложняет задачу. Код возврата, который выходит из переданной по конвейеру последовательности команд, является кодом возврата последней команды в цепочке. Если произойдет сбой с командой в середине цепочки, мы вернемся к исходной точке. Этот код возврата будет потерян, и сценарий продолжит обработку.

Мы можем видеть эффекты конвейерных команд с разными кодами возврата, используя встроенные функции оболочки true и false . Эти две команды не более чем генерируют код возврата, равный нулю или единице соответственно.

 истинный
 эхо $?
 ЛОЖЬ
 эхо $? 

Встроенные команды оболочки bash true и false.

Если мы передаем false в true — где false представляет неудачный процесс — мы получаем код возврата true , равный нулю.

 ложный | истинный
 эхо $? 

Преобразование лжи в истину.

Bash имеет переменную массива с именем PIPESTATUS , и она фиксирует все коды возврата из каждой программы в цепочке конвейеров.

 ложный | правда | ложный | истинный
 echo "${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]} ${PIPESTATUS[3]}" 

Использование PIPESTATUS для просмотра кода возврата всех программ в цепочке каналов.

PIPESTATUS хранит коды возврата только до тех пор, пока не запустится следующая программа, и попытка определить, какой код возврата соответствует той или иной программе, может очень быстро привести к путанице.

Вот тут-то и появляются set -o (параметры) и pipefail . Это «script-4.sh». Это попытается передать содержимое несуществующего файла в wc .

 #!/бин/баш 
установить -е

эхо Это произойдет первым
кот скрипт-99.sh | туалет -л
эхо Это произойдет второй

Это терпит неудачу, как мы и ожидали.

 ./скрипт-4.ш
 эхо $? 

Запуск скрипта с ошибкой в ​​цепочке каналов.

Первый ноль — это вывод wc , говорящий нам, что он не читал ни одной строки для отсутствующего файла. Второй ноль — это код возврата из второй echo -команды.

Мы добавим -o pipefail , сохраним его как «script-5.sh» и сделаем исполняемым.

 #!/бин/баш 
установить -eo pipefail

эхо Это произойдет первым
кот скрипт-99.sh | туалет -л
эхо Это произойдет второй

Давайте запустим это и проверим код возврата.

 ./скрипт-5.ш
 эхо $? 

Запуск сценария, который перехватывает ошибки в цепочках каналов и правильно устанавливает код возврата.

Сценарий останавливается, а вторая команда echo не выполняется. Код возврата, отправленный в оболочку, равен единице, правильно указывающей на сбой.

СВЯЗАННЫЕ С: Как использовать команду Echo в Linux

Перехват неинициализированных переменных

Неинициализированные переменные может быть трудно обнаружить в реальном скрипте. Если мы попытаемся echo echo напечатает пустую строку. Это не вызывает сообщение об ошибке. Остальная часть скрипта будет продолжать выполняться.

Это скрипт-6.sh.

 #!/бин/баш 
установить -eo pipefail

эхо "$ не установлено" 
echo "Еще одна эхо-команда"

Мы запустим его и понаблюдаем за его поведением.

 ./скрипт-6.ш
 эхо $? 

Запуск скрипта, который не захватывает неинициализированные переменные.

Сценарий обходит неинициализированную переменную и продолжает выполняться. Код возврата равен нулю. Попытка найти подобную ошибку в очень длинном и сложном сценарии может быть очень сложной.

Как работать с переменными в Bash
СВЯЗАННЫЕ С : Как работать с переменными в Bash

Мы можем перехватить этот тип ошибки, используя опцию set -u (unset). Мы добавим это в нашу растущую коллекцию установленных параметров в верхней части скрипта, сохраним его как «script-7.sh» и сделаем его исполняемым.

 #!/бин/баш 

установить -eou pipefail

эхо "$ не установлено" 

echo "Еще одна эхо-команда"

Запустим скрипт:

 ./скрипт-7.ш
 эхо $? 

Запуск скрипта, который захватывает неинициализированные переменные.

Обнаружена неинициализированная переменная, сценарий останавливается, а код возврата устанавливается равным единице.

Параметр -u (unset) достаточно умен, чтобы не срабатывать в ситуациях, когда вы можете законно взаимодействовать с неинициализированной переменной.

В «script-8.sh» скрипт проверяет, инициализирована ли переменная New_Var или нет. Вы не хотите, чтобы сценарий останавливался на этом, в реальном сценарии вы будете выполнять дальнейшую обработку и разбираться с ситуацией самостоятельно.

Обратите внимание, что мы добавили параметр -u в качестве второго параметра в операторе set. Опция -o pipefail должна быть последней.

 #!/бин/баш 

установить -euo pipefail

если [-z "${New_Var:-}" ]; тогда 

echo "Новая_переменная не имеет присвоенного значения." 

фи

В «script-9.sh» проверяется неинициализированная переменная, и если она неинициализирована, вместо нее предоставляется значение по умолчанию.

 #!/бин/баш
установить -euo pipefail

значение по умолчанию = 484
Значение=${Новая_переменная:-$значение_по_умолчанию}
эхо "Новая_переменная=$значение"

Сценарии разрешено выполнять до их завершения.

 ./скрипт-8.ш
 ./скрипт-9.ш 

Запуск двух сценариев, в которых неинициализированные переменные обрабатываются внутри, а параметр -u не срабатывает.

Запечатанный топором

Еще одна удобная опция — set -x (выполнить и распечатать). Когда вы пишете сценарии, это может быть спасением. он печатает команды и их параметры по мере их выполнения.

Это дает вам быструю «грубую и готовую» форму трассировки выполнения. Выделение логических ошибок и обнаружение ошибок становится намного проще.

Мы добавим параметр set -x в «script-8.sh», сохраним его как «script-10.sh» и сделаем исполняемым.

 #!/бин/баш
установить -euxo pipefail

если [-z "${New_Var:-}" ]; тогда
  echo "Новая_переменная не имеет присвоенного значения."
фи

Запустите его, чтобы увидеть линии трассировки.

 ./скрипт-10.ш 

Запуск сценария со строками трассировки -x, записанными на терминал.

Выявить ошибки в этих тривиальных примерах скриптов несложно. Когда вы начнете писать более сложные сценарии, эти опции окажутся полезными.