Отладка bash-скриптов: простые приёмы, которые экономят часы

Сложные bash-скрипты часто ломаются не из-за глобальных архитектурных просчётов, а из-за банальных мелочей: забытой кавычки, лишнего пробела или неопределённой переменной. Хорошая новость в том, что сам bash даёт достаточно инструментов, чтобы находить такие ошибки быстро и без сторонних утилит. Разберём несколько приёмов, которые стоит держать под рукой при написании и отладке shell-скриптов.

Сначала — каркас, потом — детали

Один из самых надёжных способов избежать синтаксических ошибок — писать конструкции for, while, case и if/then сразу с внешним каркасом, а уже потом заполнять его логикой. Если начать со «скелета», риск забыть закрывающий оператор (done, fi, esac) резко снижается.

for i in list          while [ condition ]   case "$1" in       if [ condition ]; then
do                      do                     str1) cmd;;
cmds                    cmds                   *) cmd;;
done                    done                   esac                  else
                                                                      cmds
                                                                      fi

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

Проверка аргументов скрипта

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

Проверка количества аргументов:

if [ $# -lt 2 ]; then
    echo "Usage: $0 lines filename"
    exit
else
    numlines=$1
    filename=$2
fi

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

Проверка типа аргумента (например, что передано число):

if [[ $numlines != [0-9]* ]]; then
    echo "Error: $numlines is not numeric"
    exit
fi

Проверка существования файла:

if [ ! -f $filename]; then
    echo "Error: File $filename not found"
    exit
fi

Включаем режим отладки bash

Ещё один способ протестировать скрипт — включить режим отладки. Он позволяет увидеть, какие команды действительно выполняются и в каком порядке, что заметно упрощает поиск проблемной строки. Отвечает за это опция -x, которая выводит каждую строку скрипта непосредственно перед её выполнением.

Использовать -x можно двумя способами. Запустить скрипт с ключом:

$ bash -x myscript

Либо добавить -x прямо в shebang-строку в начале файла:

#!/bin/bash -x

Второй вариант удобен тем, что не нужно каждый раз набирать «bash -x scriptname» — достаточно запустить скрипт как обычно. Главное — не забыть убрать -x из shebang, когда отладка закончена.

Можно включать отладку только для части скрипта, обрамив нужный фрагмент командами set -x и set +x:

set -x
while [ $i -le 2 ]
do
  echo Number: $i
  ((i++))
done
set +x

Опция -v работает похожим образом, но показывает каждую строку скрипта по мере её чтения, а не выполнения — то есть сначала виден весь код, а затем результат его работы. Обе опции можно объединить: bash -xv.

Разбор реальной ошибки на примере

Вот как выглядит вывод bash -x для скрипта showtop, который должен показывать первые строки файла:

$ bash -x showtop
++ date +%s
+ STARTTIME=1622138266
++ - 1622138266
showtop: line 4: -: command not found
+ echo 'It takes  seconds to complete this task...'
It takes  seconds to complete this task...
+ '[' 0 -lt 2 ']'
+ echo 'Usage: showtop lines filename'
Usage: showtop lines filename
+ exit

Уже четвёртая строка вывода намекает на проблему: bash ищет несуществующую команду «-». Причина — в исходном коде:

#!/bin/bash

STARTTIME=$(date +%s)
timing=`expr $ENDTIME - $STARTTIME`
echo "It takes $timing seconds to complete this task"

Переменная $ENDTIME на этом этапе ещё не имеет значения, поэтому вычисление превращается в бессмысленное «- 1622138266» и завершается ошибкой. Расчёт времени выполнения должен идти не в начале скрипта, а в конце — после того как конечное время действительно зафиксировано. Исправленная версия выглядит так:

#!/bin/bash

STARTTIME=$(date +%s)

if [ $# -lt 2 ]; then
    echo "Usage: $0 lines filename"
    exit
else
    numlines=$1
    filename=$2
fi

if [[ $numlines != [0-9]* ]]; then
    echo "Error: $numlines is not numeric"
    exit
fi

if [ ! -f $filename ]; then
    echo "Error: File $filename not found"
    exit
else
    echo top of file
    head -$numlines $filename
fi

ENDTIME=$(date +%s)
timing=`expr $ENDTIME - $STARTTIME`
echo "It took $timing seconds to complete this task"

А вот пример запуска исправленного скрипта в режиме отладки — строки собственно вывода скрипта перемешаны со служебными строками трассировки:

$ bash -x showtop 10 oldScript
++ date +%s
+ STARTTIME=1622143586
+ '[' 2 -lt 2 ']'
+ numlines=3
+ filename=oldScript
+ [[ 3 != [0-9]* ]]
+ '[' '!' -f oldScript ']'
+ echo top of file
top of file
+ head -3=10 oldScript
++ date +%s
+ ENDTIME=1622143586
++ expr 1622143586 - 1622143586
+ timing=0
+ echo 'It took 0 seconds to complete this task...'
It took 0 seconds to complete this task...

Итог

Проверка количества и типа аргументов, а также контроль существования файлов избавляют скрипт от значительной части runtime-ошибок ещё до того, как они возникнут. А режим -x (при необходимости — в паре с -v) остаётся, пожалуй, самым быстрым способом понять, что именно пошло не так — особенно когда скрипт гораздо длиннее и сложнее, чем разобранный здесь пример.

Источник: NetworkWorld.