Linux. Отладка bash-скриптов
Отладка скрипта целиком
Когда скрипт ведет себя не так, как планировалось, необходимо определить, из-за чего возникли проблемы. Наиболее распространенным способом является запуск подоболочки с опцией -x
, благодаря которому весь скрипт будет запущен в отладочном режиме. После того, как для каждой команды будут выполнены все необходимые подстановки и замены, но перед тем, как команда будет выполнена, в стандартный выходной поток будет выдана трассировка команды и все ее аргументы.
Допустим, мы написали небольшой скрипт для поиска строк по регулярному выражению в файле с данными
#!/bin/bash
file=$1
find=$2
cat $file | egrep -i $find
[ $? -eq 1 ] && echo "String $find not found in file $file"
$ cat unix.txt The Unix operating system was pioneered by Ken Thompson and Dennis Ritchie at Bell Laboratories in the late 1960s. One of the primary goals in the design of the Unix system was to create an environment that promoted efficient program development.
И пробуем его запустить, по ошибке пропуская второй параметр
$ ./search.sh unix.txt Использование: grep [ПАРАМЕТР]… ШАБЛОНЫ [ФАЙЛ]… Запустите «grep --help» для получения более подробного описания.
Не очень понятно, что случилось. Давайте теперь запустим скрипт в режиме отладки.
$ bash -x search.sh unix.txt + file=unix.txt + find= + egrep -i + cat unix.txt Использование: grep [ПАРАМЕТР]… ШАБЛОНЫ [ФАЙЛ]… Запустите «grep --help» для получения более подробного описания.
Каждая команда, перед ее выполнением, выводится в консоль. Мы видим, что переменной file
было присвоено значение unix.txt
, переменная find
осталась пустой, а утилита egrep
не получила строку регулярного выражения для поиска.
Пробуем запустить еще раз — на это раз с двумя параметрами
$ bash -x search.sh unix.txt 'unix' + file=unix.txt + find=unix + egrep -i unix + cat unix.txt The Unix operating system was pioneered by Ken the design of the Unix system was to create an
Отладка скрипта по частям
С помощью встроенной команды set
можно запускать в отладочном режиме только подозрительные части скрипта. В одном скрипте можно включать и выключать отладочный режим столько раз, сколько это необходимо.
#!/bin/bash
file=$1
find=$2
set -x
cat $file | egrep -i $find
set +x
[ $? -eq 1 ] && echo "String $find not found in file $file"
Пробуем запустить скрипт, опять пропуская второй параметр
$ ./search.sh unix.txt + egrep -i Использование: grep [ПАРАМЕТР]… ШАБЛОНЫ [ФАЙЛ]… Запустите «grep --help» для получения более подробного описания. + cat unix.txt
Видим, что утилита egrep
не получила строку регулярного выражения для поиска.
Поиск синтаксических ошибок
При написании скрипта могут быть допущены ошибки — но это легко проверить. Для примера, в скрипте пропущена закрывающая кавычка для команды echo
.
$ cat search.sh #!/bin/bash echo 'Search string in file file=$1 find=$2 cat $file | egrep -i $find [ $? -eq 1 ] && echo "String $find not found in file $file"
$ bash -n search.sh search.sh: строка 2: неожиданный конец файла во время поиска «'» search.sh: строка 7: синтаксическая ошибка: неожиданный конец файла
Bash не будет выполянить скрипт, а только проверит на предмет наличия синтаксических ошибок.
Многословный режим запуска
Скрипт может быть запущен в verbose mode — это значит, что bash
будет выводить каждую команду перед ее выполнением.
#!/bin/bash
file=$1
find=$2
cat $file | egrep -i $find
[ $? -eq 1 ] && echo "String $find not found in file $file"
$ bash -v search.sh unix.txt 'unix' #!/bin/bash file=$1 find=$2 cat $file | egrep -i $find The Unix operating system was pioneered by Ken the design of the Unix system was to create an [ $? -eq 1 ] && echo "String $find not found in file $file"
Отладка с помощью ShellCheck
ShellCheck — это статический анализатор кода, который написан на Haskell. Он помогает искать ошибки в скриптах и дает полезные советы по улучшению кода.
$ sudo apt install shellcheck
Допустим, у нас есть простой скрипт бэкапа — давайте запустим ShellCheck и посмотрим, что он нам посоветует.
#!/bin/bash
SOURCE=/home/evgeniy/data
BACKUP=/home/evgeniy/backup
COUNT=30 # кол-во бэкапов
rm -rf ${BACKUP}/backup-${COUNT}
for (( i = $COUNT; i > 1; i-- )) ; do
mv ${BACKUP}/backup-$((i - 1)) ${BACKUP}/backup-${i}
done
rsync --archive --link-dest=../backup-2 ${SOURCE}/ ${BACKUP}/backup-1
$ shellcheck backup.sh In backup.sh line 7: for (( i = $COUNT; i > 1; i-- )) ; do ^----^ SC2004 (style): $/${} is unnecessary on arithmetic variables. For more information: https://www.shellcheck.net/wiki/SC2004 -- $/${} is unnecessary on arithmeti...
Внутри ((...))
можно обращаться к переменным без использования знака $
. Ошибок нет, но мы получили совет по улучшению скрипта.
Быстрое завершение при ошибке
Очень важно реагировать на ошибки, как только они возникают, и быстро прекращать выполнение. Ничего не может быть хуже, чем продолжать выполнение команды ниже, когда переменная $dirname
не определена.
rm -rf ${dirname}/*
Для предотвращения таких случаев можно использовать встроенную команду set
.
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
rm -rf ${dirname}/*
Это гарантирует, что скрипт завершит работу, как только встретит ненулевой код завершения, использование неопределенных переменных или неправильные команды, переданные по каналу. Если логика скрипта допускает выполнение команды с ненулевым кодом возврата — это нужно предусмотреть.
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
rmdir '/directory/not/exists' || echo 'Ошибка удаления директории, скрипт продолжит работу'
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
if ! rmdir '/directory/not/exists'; then
echo 'Ошибка удаления директории, скрипт продолжит работу'
fi