Сложные 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.