Поиск по сайту:

Отладка Bash-скриптов

Отладка Bash-скриптов

Отладка помогает исправить ошибки в вашей программе. В этой статье мы обсудим различные методы отладки сценариев Bash в операционных системах Linux и Unix.

Введение

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

Знание того, как использовать правильную технику отладки, поможет вам быстро устранить ошибки. В отличие от других языков, таких как Python, Java и т. д., для bash нет инструмента отладки, с помощью которого можно было бы устанавливать точки останова, выполнять пошаговый код и т. д.

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

Три способа использования параметров отладки

Если вы хотите включить параметры отладки в своих сценариях, вы можете сделать это тремя способами.

1. Включите параметры отладки из оболочки терминала при вызове сценария.

bash [ debugging flags ] scriptname

2. Включите параметры отладки, передав флаги отладки в строку shebang в сценарии.

#!/bin/bash [ debugging flags ]

3. Включите параметры отладки с помощью команды set из сценария.

set -o nounset
set -u

Для чего используется команда Set?

Команда set — это встроенная команда оболочки, которую можно использовать для управления параметрами bash и определенным образом изменять поведение bash.

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

type -a set
set is a shell builtin

Вы можете получить доступ к разделу справки команды set, чтобы узнать, какие флаги она поддерживает и что делает каждый флаг.

set --help

Отладка части скрипта или полного скрипта

Прежде чем изучать возможности отладки, вы должны понимать, что вы можете отлаживать либо весь скрипт, либо только определенную часть кода. Вам необходимо использовать команду set, чтобы включать и отключать параметры отладки.

  • set - включит режим отладки.
  • set + отключит режим отладки.

Взгляните на приведенный ниже код. set -x включит режим xtrace для скрипта, а set +x отключит режим xtrace. Все, что находится между set -x и set +x, будет выполняться в режиме отладки xtrace.

Вы узнаете о режиме xtrace в следующем разделе. Поэтому для любого флага отладки единственное, что вам нужно запомнить, это то, что set - включит режим, а set + отключит этот режим.

#!/bin/bash

set -x
read -p "Pass Dir name : " D_OBJECT
read -p "Pass File name : " F_OBJECT
set +x

touch ${D_OBJECT}/${F_OBJECT}

Ошибка, если переменная не определена

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

Взгляните на приведенный ниже код, в котором я получаю данные от пользователя и сохраняю их в переменной $OBJECT. Я попытался запустить оператор тестирования (-f и -d) для переменной $OBJECT1, которая не определена.

#!/bin/bash

read -p "Please provide the object name :  " OBJECT
if [[ -f $OBJECT1 ]]
then
    echo "$OBJECT is a file"
elif [[ -d $OBJECT1 ]]
then
    echo "$OBJECT is a directory"
fi

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

Скрипт успешно завершен

Чтобы переопределить это поведение, используйте флаг -u, который выдаст ошибку при использовании неопределенной переменной.

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

Несвязанная переменная

Вы также можете установить параметр -u с помощью команды set или передать его в качестве аргумента shebang.

set -u
set -o nounset

(или)

#! /bin/bash -u

Режим Xtrace спешит на помощь

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

В предыдущем разделе, когда я запускал код без флага -u, он завершился успешно, но я ожидал вывода в терминале. Теперь я могу запустить тот же сценарий в режиме xtrace и точно увидеть, где в сценарии возникает проблема.

Взгляните на следующий пример кода.

#!/bin/bash

read -p "Please provide the object name :  " OBJECT
if [[ -f $OBJECT1 ]]
then
    echo "$OBJECT is a file"
elif [[ -d $OBJECT1 ]]
then
    echo "$OBJECT is a directory"
fi

Когда я запускаю приведенный выше код, он не возвращает мне никаких результатов.

Логическая ошибка

Чтобы устранить эту проблему, я могу запустить сценарий в режиме xtrace, передав флаг -x.

В приведенном ниже выводе вы можете видеть, что переменные развернуты и распечатаны. Это говорит мне о том, что условным операторам -f и -d присвоены пустые строки. Таким образом, я могу логически проверить и исправить ошибки.

Режим Xtrace

Знак плюса, который вы видите в выводе, можно изменить, установив в скрипте переменную PS4. По умолчанию для PS4 установлено значение (+).

echo $PS4
+
PS4=" ==> " bash -x debugging.sh

Установить или изменить переменную PS4

Вы также можете установить режим Xtrace с помощью команды set или передать его в качестве аргумента shebang.

set -x
set -o xtrace

(или)

#! /bin/bash -x

Аналогичным образом, при отладке вы можете перенаправить журналы отладки Xtrace в файл вместо того, чтобы печатать их на терминале.

Взгляните на приведенный ниже код. Я назначаю файловый дескриптор 6 файлу .log, и BASH_XTRACEFD="6" перенаправит журналы отладки xtrace на файловый дескриптор 6.

#!/bin/bash


exec 6> redirected_debug.log 
PS4=' ==> ' 
BASH_XTRACEFD="6" 
read -p "Please provide the object name :  " OBJECT
if [[ -f $OBJECT1 ]]
then
    echo "$OBJECT is a file"
elif [[ -d $OBJECT1 ]]
then
    echo "$OBJECT is a directory"
fi

Когда я запускаю этот код вместо печати вывода xtrace в терминале, он будет перенаправлен в файл .log.

$ cat redirected_debug.log 
==> read -p 'Please provide the object name :  ' OBJECT
==> [[ -f '' ]]
==> [[ -d '' ]]

Статус выхода PIPE

Поведение по умолчанию при использовании канала заключается в том, что он принимает код завершения последней команды запуска в канале. Даже если предыдущие команды в канале завершились неудачно, он запустит остальную часть канала.

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

Если вы попытаетесь проверить код завершения последней команды канала запуска с помощью $?, вы получите ноль в качестве кода завершения, полученного из программы подсчета слов.

$ cat nofile.txt | wc -l
cat: nofile.txt: No such file or directory
0
$ echo $?
0

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

set -o pipefail

ТРУБА Статус выхода

С этим подходом все еще существует проблема. Обычно следует ожидать, что если какая-либо команда в канале завершилась неудачно, сценарий должен завершиться без выполнения остальной части команды в канале.

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

Строгий режим Bash

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

Все эти варианты мы уже подробно обсуждали в предыдущем разделе.

  • -e flag => Выйти из сценария, если какая-либо команда выдает ненулевой код выхода.
  • -u flag => Вызывает сбой сценария, если используется неопределенное имя переменной.
  • pipefail => Если какая-либо команда в конвейере завершается неудачно, код выхода будет рассматриваться для всего конвейера.
  • IFS => Внутренний разделитель полей, установка для него значений новой строки (\n) и (\t) приведет к тому, что разделение произойдет только в новой строке и табуляции.
set -e
set -u
set -o pipefail

Или

set -euo pipefail
IFS=$'\n\t'

Захват сигналов с помощью TRAP

Trap позволяет вам перехватывать сигналы в ваш bash-скрипт и предпринимать соответствующие действия.

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

Взгляните на псевдокод, приведенный ниже. Я создал функцию очистки, которая будет запускаться, когда в скрипт передается SIGINT.

trap 'cleanup' TERM INT
function cleanup(){
    echo "Running cleanup since user initiated CTRL + C"
    <some logic>
}

Вы можете использовать ловушку «DEBUG», которую можно использовать для многократного выполнения оператора в сценарии. Он ведет себя следующим образом: для каждого оператора, который выполняется в ловушке сценария, запускается связанная функция или оператор.

Вы можете понять это, используя приведенный ниже пример.

#!/bin/bash

trap 'printf "${LINENO} ==> DIR_NAME=${D_OBJECT} ; FILE_NAME=${F_OBJECT}; FILE_CREATED=${FILE_C} \n"' DEBUG

read -p "Pass Dir name : " D_OBJECT
read -p "Pass File name : " F_OBJECT

touch ${D_OBJECT}/${F_OBJECT} && FILE_C="Yes"
exit 0

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

Посмотрите вывод ниже. Для каждой строки скрипта срабатывает ловушка и соответствующим образом обновляются переменные.

Запуск TRAP для каждого оператора

Распечатайте код в подробном режиме

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

Взгляните на следующую программу. Это простая программа, которая получает объект от пользователя и проверяет, является ли переданный объект файлом или каталогом, с помощью условного оператора.

#!/bin/bash

read -p "Please provide the object name :  " OBJECT
if [[ -f $OBJECT ]]
then
  echo "$OBJECT is a file"
elif [[ -d $OBJECT ]]
then
  echo "$OBJECT is a directory"
fi

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

Подробный режим

Как только я передам объект, остальная часть кода будет напечатана, а затем будет выведен результат.

Распечатайте код в подробном режиме

Вы также можете установить подробный режим с помощью set или shebang.

set -v
set -o verbose

(или)

#! /bin/bash -v

Вы также можете комбинировать подробный режим с другими режимами.

set -vx # Verbose and Xtrace Mode
set -uv # Verbose and Unset Mode

Проверка синтаксиса — режим noexec

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

Синтаксические ошибки очень распространены в программах. Возможно, вы пропустили кавычку, не смогли выйти из цикла и т. д. Вы можете использовать флаг «-n», который называется noexec mode, чтобы проверьте синтаксис перед запуском программы.

Я собираюсь запустить приведенный ниже фрагмент кода и проверить синтаксис.

#!/bin/bash

TOOLS=( htop peek tilix vagrant shutter )
for TOOL in "${TOOLS[@]" 
do
    echo "--------------INSTALLING: ${TOOL}---------------------------"
    apt install ${TOOL} -y
#done

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

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

bash -n ./debugging.sh 

./debugging.sh: line 6: unexpected EOF while looking for matching `"'
./debugging.sh: line 8: syntax error: unexpected end of file

Следует отметить, что по умолчанию при запуске сценария bash проверяет синтаксис и выдает эти ошибки даже без использования режима noexec.

Альтернативно вы также можете использовать команду set или shebang, чтобы использовать режим noexec.

set -n 
set -o noexec

Или,

#! /bin/bash -n

Существует несколько внешних инструментов, на которые стоит обратить внимание и которые используются для отладки сценариев. Одним из таких инструментов является Shellcheck. Shellcheck также можно интегрировать с популярными текстовыми редакторами, такими как vscode, sublime text, Atom.

Заключение

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

Руководства по написанию сценариев Bash:

  • Сценарии Bash: анализ аргументов в сценариях Bash с помощью getopts
  • Как создавать диалоговые окна графического пользовательского интерфейса в сценариях Bash с помощью Zenity в Linux и Unix
  • Сценарии Bash – утверждение случая
  • Сценарии Bash – условные операторы
  • Скрипты Bash: манипулирование строками
  • Сценарии Bash: команда Printf с примерами
  • Скрипты Bash: индексированные массивы с примерами
  • Скрипты Bash: объяснение ассоциативных массивов на примерах
  • Скрипты Bash: цикл For с примерами
  • Скрипты Bash: циклы while и Until с примерами
  • Перенаправление Bash, объясненное примерами
  • Скрипты Bash: переменные, поясняемые примерами
  • Скрипты Bash: функции, поясняемые примерами
  • Команда Bash Echo с примерами для Linux
  • Руководство по Bash Heredoc для начинающих

Статьи по данной тематике