Командная оболочка Bash не имеет своего отладчика, и не имеет даже каких либо отладочных команд или конструкций. [59] Синтаксические ошибки или опечатки часто вызывают сообщения об ошибках, которые которые практически никак не помогают при отладке.
Пример 29-1. Сценарий, содержащий ошибку
#!/bin/bash # ex74.sh # Этот сценарий содержит ошибку. a=37 if [$a -gt 27 ] then echo $a fi exit 0
В результате исполнения этого сценария вы получите такое сообщение:
./ex74.sh: [37: command not found
Что в этом сценарии может быть неправильно (подсказка: после ключевого слова if)?
Пример 29-2. Пропущено ключевое слово
#!/bin/bash # missing-keyword.sh: # Какое сообщение об ошибке будет выведено, при попытке запустить этот сценарий? for a in 1 2 3 do echo "$a" # done # Необходимое ключевое слово 'done' закомментировано. exit 0
На экране появится сообщение:
>missing-keyword.sh: line 11: syntax error: unexpected end of file
Обратите внимание, сообщение об ошибке будет содержать номер не той строки, в которой возникла ошибка, а той, в которой Bash точно установил наличие ошибочной ситуации.
Сообщения об ошибках могут вообще не содержать номера строки, при исполнении которой эта ошибка появилась.
А что делать, если сценарий работает, но не так как ожидалось? Вот пример весьма распространенной логической ошибки.
#!/bin/bash # Ожидается, что этот сценарий будет удалять в текущем каталоге #+ все файлы, имена которых содержат пробелы. # Но он не работает. Почему? badname=`ls | grep ' '` # echo "$badname" rm "$badname" exit 0
Попробуйте найти ошибку, раскомментарив строку echo "$badname". Инструкция echo очень полезна при отладке сценариев, она позволяет узнать -- действительно ли вы получаете то, что ожидали получить.
В данном конкретном случае, команда rm "$badname" не дает желаемого результата потому, что переменная $badname взята в кавычки. В результате, rm получает единственный аргумент (т.е. команда будет считать, что получила имя одного файла). Частично эта проблема может быть решена за счет удаления кавычек вокруг $badname и установки переменной $IFS так, чтобы она содержала только символ перевода строки, IFS=$'\n'. Однако, существует более простой способ выполнить эту задачу.
# Правильный способ удаления файлов, в чьих именах содержатся пробелы. rm *\ * rm *" "* rm *' '* # Спасибо S.C.
В общих чертах, ошибочными можно считать такие сценарии, которые
-
"сыплют" сообщениями о "синтаксических ошибках" или
-
запускаются, но работают не так как ожидалось (логические ошибки).
-
запускаются, делают то, что требуется, но имеют побочные эффекты (логическая бомба).
Инструменты, которые могут помочь при отладке неработающих сценариев
-
команда echo, в критических точках сценария, поможет отследить состояние переменных и отобразить ход исполнения.
-
команда-фильтр tee, которая поможет проверить процессы и потоки данных в критических местах.
-
ключи -n -v -x
sh -n scriptname -- проверит наличие синтаксических ошибок, не запуская сам сценарий. Того же эффекта можно добиться, вставив в сценарий команду set -n или set -o noexec. Обратите внимание, некоторые из синтаксических ошибок не могут быть выявлены таким способом.
sh -v scriptname -- выводит каждую команду прежде, чем она будет выполнена. Того же эффекта можно добиться, вставив в сценарий команду set -v или set -o verbose.
Ключи -n и -v могут употребляться совместно: sh -nv scriptname.
sh -x scriptname -- выводит, в краткой форме, результат исполнения каждой команды. Того же эффекта можно добиться, вставив в сценарий команду set -x или set -o xtrace.
Вставив в сценарий set -u или set -o nounset, вы будете получать сообщение об ошибке unbound variable всякий раз, когда будет производиться попытка обращения к необъявленной переменной.
-
Функция "assert", предназначенная для проверки переменных или условий, в критических точках сценария. (Эта идея заимствована из языка программирования C.)
Пример 29-4. Проверка условия с помощью функции "assert"
# #!/bin/bash # assert.sh assert () # Если условие ложно, { #+ выход из сценария с сообщением об ошибке. E_PARAM_ERR=98 E_ASSERT_FAILED=99 if [ -z "$2" ] # Недостаточное количество входных параметров. then return $E_PARAM_ERR fi lineno=$2 if [ ! $1 ] then echo "Утверждение ложно: \"$1\"" echo "Файл: \"$0\", строка: $lineno" exit $E_ASSERT_FAILED # else # return # и продолжить исполнение сценария. fi } a=5 b=4 condition="$a -lt $b" # Сообщение об ощибке и завершение сценария. # Попробуйте поменять условие "condition" #+ на что нибудь другое и #+ посмотреть -- что получится. assert "$condition" $LINENO # Сценарий продолжит работу только в том случае, если утверждение истинно. # Прочие команды. # ... echo "Эта строка появится на экране только если утверждение истинно." # ... # Прочие команды. # ... exit 0 #
-
Ловушка на выхто в этом сценарии может быть неправильно (подсказка: после ключевого словоде.
Команда exit, в сценарии, порождает сигнал 0, по которому процесс завершает работу, т.е. -- сам сценарий. [60] Часто бывает полезным по выходу из сценария выдать "распечатку" переменных.
- trap
-
Определяет действие при получении сигнала; так же полезна при отладке.
trap '' 2 # Игнорировать прерывание 2 (Control-C), действие по сигналу не указано. trap 'echo "Control-C disabled."' 2 # Сообщение при нажатии на Control-C.
Пример 29-5. Ловушка на выходе
#!/bin/bash trap 'echo Список переменных --- a = $a b = $b' EXIT # EXIT -- это название сигнала, генерируемого при выходе из сценария. a=39 b=36 exit 0 # Примечательно, что если закомментировать команду 'exit', # то это никак не скажется на работе сценария, # поскольку "выход" из сценария происходит в любом случае.
Пример 29-6. Удаление временного файла при нажатии на Control-C
#!/bin/bash # logon.sh: Сценарий, написаный "на скорую руку", контролирует вход в режим on-line. TRUE=1 LOGFILE=/var/log/messages # Обратите внимание: $LOGFILE должен быть доступен на чтение (chmod 644 /var/log/messages). TEMPFILE=temp.$$ # "Уникальное" имя для временного файла, где расширение в имени -- это pid процесса-сценария. KEYWORD=address # При входе, в файл /var/log/messages, # добавляется строка "remote IP address xxx.xxx.xxx.xxx" ONLINE=22 USER_INTERRUPT=13 CHECK_LINES=100 # Количество проверяемых строк. trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT # Удалить временный файл, когда сценарий завершает работу по control-c. echo while [ $TRUE ] #Бесконечный цикл. do tail -$CHECK_LINES $LOGFILE> $TEMPFILE # Последние 100 строк из системного журнала переписать во временный файл. # Совершенно необходимо, т.к. новейшие версии ядер генерируют много сообщений при входе. search=`grep $KEYWORD $TEMPFILE` # Проверить наличие фразы "address", # свидетельствующей об успешном входе. if [ ! -z "$search" ] # Кавычки необходимы, т.к. переменная может содержать пробелы. then echo "On-line" rm -f $TEMPFILE # Удалить временный файл. exit $ONLINE else echo -n "." # ключ -n подавляет вывод символа перевода строки, # так вы получите непрерывную строку точек. fi sleep 1 done # Обратите внимание: если изменить содержимое переменной KEYWORD # на "Exit", то сценарий может использоваться для контроля # неожиданного выхода (logoff). exit 0 # Nick Drage предложил альтернативный метод: while true do ifconfig ppp0 | grep UP 1> /dev/null && echo "соединение установлено" && exit 0 echo -n "." # Печать последовательности точек (.....), пока соединение не будет установлено. sleep 2 done # Проблема: Нажатия Control-C может оказаться недостаточным, чтобы завершить этот процесс. # (Точки продолжают выводиться на экран.) # Упражнение: Исправьте этот недостаток. # Stephane Chazelas предложил еще одну альтернативу: CHECK_INTERVAL=1 while ! tail -1 "$LOGFILE" | grep -q "$KEYWORD" do echo -n . sleep $CHECK_INTERVAL done echo "On-line" # Упражнение: Найдите сильные и слабые стороны # каждого из этих подходов.
Конструкция trap '' SIGNAL (две одиночных кавычки) -- запрещает SIGNAL для оставшейся части сценария. Конструкция trap SIGNAL -- восстанавливает действие сигнала SIGNAL. Эти конструкции могут использоваться для защиты критических участков сценария от нежелательного прерывания. |
trap '' 2 # Сигнал 2 (Control-C) -- запрещен. command command command trap 2 # Разрешение реакции на Control-C
Notes
[59] |
Bash debugger (автор: Rocky Bernstein) частично возмещает этот недостаток. |
[60] |
В соответствии с соглашениями, сигнал с номером 0 соответствует команде exit. |