355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Уильям Шоттс » Командная строка Linux » Текст книги (страница 26)
Командная строка Linux
  • Текст добавлен: 12 апреля 2017, 12:30

Текст книги "Командная строка Linux"


Автор книги: Уильям Шоттс


Жанр:

   

ОС и Сети


сообщить о нарушении

Текущая страница: 26 (всего у книги 30 страниц)

29. Управление потоком выполнения: циклы while и until

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

В этой главе мы познакомимся с приемами организации циклов, с помощью которых можно реализовать многократное выполнение участков программ. Командная оболочка поддерживает три составные команды для организации циклов. Здесь мы познакомимся с двумя из них, а с третьей – в главе 33.

Циклы

Повседневная жизнь наполнена повторяющимися действиями. Каждодневная поездка на работу, прогулка с собакой и нарезание моркови – все эти действия состоят из повторяющейся последовательности действий. Рассмотрим в качестве примера резку моркови. Этот вид деятельности можно выразить на псевдокоде примерно так:

1. Взять разделочную доску.

2. Взять нож.

3. Положить морковь на доску.

4. Поднять нож.

5. Сдвинуть морковь.

6. Отрезать кусок.

7. Если вся морковь порезана, завершить операцию, иначе перейти к шагу 4.

Шаги с 4-го по 7-й образуют цикл. Действия внутри цикла повторяются, пока не будет выполнено условие «вся морковь порезана».

while

В bash имеются средства, позволяющие выражать похожие идеи. Представьте, что нам нужно вывести пять чисел по порядку, от 1 до 5. В сценарии на языке bash это можно реализовать, как показано ниже:

#!/bin/bash

# while-count: вывод последовательности чисел

count=1

while [ $count -le 5 ]; do

        echo $count

        count=$((count + 1))

done

echo "Finished."

Если запустить этот сценарий, он выведет:

[me@linuxbox ~]$ while-count

1

2

3

4

5

Finished.

Команда while имеет следующий синтаксис:

while команды; do команды; done

Подобно if, команда while проверяет код завершения списка команд. Пока код завершения равен 0, она выполняет команды внутри цикла. В сценарии, приведенном выше, создается переменная count, и ей присваивается начальное значение 1. Команда while проверяет код завершения команды test. Пока test возвращает код 0, команды внутри цикла продолжают выполняться. В конце каждого цикла повторно выполняется команда test. После шести итераций цикла значение переменной count увеличится до 6, команда test вернет код завершения, отличный от 0, и цикл завершится, а программа продолжит выполнение с инструкции, следующей непосредственно за циклом.

Цикл while можно использовать для усовершенствования программы read-menu из главы 28:

#!/bin/bash

# while-menu: программа вывода системной информации,

#             управляемая с помощью меню

DELAY=3 # Время отображения результатов на экране (в секундах)

while [[ $REPLY != 0 ]]; do

        clear

        cat <<– _EOF_

                Please Select:

                1. Display System Information

                2. Display Disk Space

                3. Display Home Space Utilization

                0. Quit

        _EOF_

        read -p "Enter selection [0-3] > "

        if [[ $REPLY =~ ^[0-3]$ ]]; then

                if [[ $REPLY == 1 ]]; then

                        echo "Hostname: $HOSTNAME"

                        uptime

                        sleep $DELAY

                fi

                if [[ $REPLY == 2 ]]; then

                        df -h

                        sleep $DELAY

                fi

                if [[ $REPLY == 3 ]]; then

                        if [[ $(id -u) -eq 0 ]]; then

                                echo "Home Space Utilization (All Users)"

                                du -sh /home/*

                        else

                                echo "Home Space Utilization ($USER)"

                                du -sh $HOME

                        fi

                        sleep $DELAY

                fi

        else

                echo "Invalid entry."

                sleep $DELAY

        fi

done

echo "Program terminated."

Заключив меню в цикл while, мы смогли заставить программу повторять вывод меню после каждой операции выбора. Цикл продолжает выполняться и выводить меню, пока переменная REPLY не получит значение 0, предоставляя пользователю возможность сделать другой выбор. После выполнения выбранной операции выполняется команда sleep, она приостанавливает программу на несколько секунд и дает возможность увидеть результаты до того, как экран будет очищен и на нем вновь появится меню. Когда переменная REPLY получит значение 0, соответствующее варианту «Quit» (выйти), цикл завершится и выполнение продолжится со строки, следующей за done.

Прерывание цикла

В bash имеются две встроенные команды для управления потоком выполнения внутри циклов. Команда break немедленно завершает цикл, после чего выполнение программы продолжается с первой инструкции, следующей за циклом. Команда пропускает оставшуюся часть цикла, и программа переходит к началу следующей итерации цикла. Ниже приводится версия программы while-menu, использующая обе команды – break и continue:

#!/bin/bash

# while-menu2: программа вывода системной информации,

#              управляемая с помощью меню

DELAY=3 # Время отображения результатов на экране (в секундах)

while true; do

        clear

        cat <<– _EOF_

                Please Select:

                1. Display System Information

                2. Display Disk Space

                3. Display Home Space Utilization

                0. Quit

        _EOF_

        read -p "Enter selection [0-3] > "

        if [[ $REPLY =~ ^[0-3]$ ]]; then

                if [[ $REPLY == 1 ]]; then

                        echo "Hostname: $HOSTNAME"

                        uptime

                        sleep $DELAY

                        continue

                fi

                if [[ $REPLY == 2 ]]; then

                        df -h

                        sleep $DELAY

                        continue

                fi

                if [[ $REPLY == 3 ]]; then

                        if [[ $(id -u) -eq 0 ]]; then

                                echo "Home Space Utilization (All Users)"

                                du -sh /home/*

                        else

                                echo "Home Space Utilization ($USER)"

                                du -sh $HOME

                        fi

                        sleep $DELAY

                        continue

                fi

                if [[ $REPLY == 0 ]]; then

                        break

                fi

        else

                echo "Invalid entry."

                sleep $DELAY

        fi

done

echo "Program terminated."

В этой версии сценария используется бесконечный цикл (цикл, который никогда не завершится сам по себе), в котором команда while проверяет код завершения команды true. Так как true всегда возвращает код 0, цикл никогда не завершится. Этот прием на удивление широко используется в сценариях. Поскольку цикл никогда не завершится сам по себе, программист должен предусмотреть его принудительное прерывание в нужный момент времени. В этом сценарии выход из цикла осуществляется с помощью команды break, когда пользователь выберет пункт 0. В конец других операций добавлена команда continue, чтобы увеличить эффективность работы сценария. Встретив команду continue, сценарий перепрыгнет через остальной код в цикле, который не требуется выполнять для данного выбора. Например, если пользователь выбрал пункт 1, нет никаких причин проверять выбор остальных вариантов.

until

Команда until очень похожа на while, но завершает цикл не когда обнаружит ненулевой код завершения, а наоборот. Цикл until продолжается, пока не получит код завершения 0. В сценарии while-count цикл продолжает выполняться, пока значение переменной count меньше или равно 5. Тот же результат можно получить, переписав сценарий с командой until:

#!/bin/bash

# until-count: вывод последовательности чисел

count=1

until [ $count -gt 5 ]; do

        echo $count

        count=$((count + 1))

done

echo "Finished."

С условным выражением $count -gt 5 команда until завершит цикл в нужный момент времени. Выбор между циклами while и until обычно зависит от того, в каком случае условное выражение будет более читабельным.

Чтение файлов в циклах

Команды while и until могут принимать данные со стандартного ввода. Это дает возможность обрабатывать файлы с их помощью. В следующем примере мы выведем содержимое файла distros.txt, созданного в одной из предыдущих глав:

#!/bin/bash

# while-read: чтение строк из файла

while read distro version release; do

        printf "Distro: %stVersion: %stReleased: %sn"

                $distro

                $version

                $release

done < distros.txt

Чтобы перенаправить файл в цикл, мы поместили оператор перенаправления после инструкции done. Цикл будет вводить поля из указанного файла с помощью read. После ввода каждой строки команда read будет завершаться с кодом 0, пока не достигнет конца файла. В этот момент она вернет ненулевой код завершения, и цикл завершится. Цикл можно также использовать в конвейерах:

#!/bin/bash

# while-read2: чтение строк из файла

sort -k 1,1 -k 2n distros.txt | while read distro version release; do

        printf "Distro: %stVersion: %stReleased: %sn"

                $distro

                $version

                $release

done

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

Заключительное замечание

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

30. Поиск и устранение ошибок

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

Синтаксические ошибки

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

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

#!/bin/bash

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

if [ $number = 1 ]; then

        echo "Number is equal to 1."

else

        echo "Number is not equal to 1."

fi

В текущем своем виде сценарий выполняется без ошибок:

[me@linuxbox ~]$ trouble

Number is equal to 1.

Отсутствующие кавычки

Давайте изменим сценарий, удалив кавычку в конце аргумента первой команды echo:

#!/bin/bash

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

if [ $number = 1 ]; then

        echo "Number is equal to 1.

else

        echo "Number is not equal to 1."

fi

Посмотрите, что из этого получилось:

[me@linuxbox ~]$ trouble

./trouble: строка 10: неожиданный EOF при поиске соответствующего `"'

./trouble: строка 13: ошибка синтаксиса: неожиданный конец файла

Командная оболочка сгенерировала два сообщения об ошибках. Обратите внимание, что номера строк в сообщениях не соответствуют номеру строки, где отсутствует кавычка. Понять причину можно, мысленно последовав за программой после отсутствующей кавычки. bash продолжит поиск закрывающей кавычки и найдет ее сразу за второй командой echo. После этого командная оболочка bash очень удивится, обнаружив нарушение синтаксиса команды if, потому что инструкция fi теперь окажется внутри строки в кавычках (незакрытой).

Найти такие ошибки в длинных сценариях порой очень сложно. Хорошую помощь в этом случае может оказать текстовый редактор с подсветкой синтаксиса. Если в системе установлена полная версия редактора vim, подсветка синтаксиса в нем включается командой:

:syntax on

Отсутствующие или неожиданные лексемы

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

#!/bin/bash

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

if [ $number = 1 ] then

        echo "Number is equal to 1."

else

        echo "Number is not equal to 1."

fi

При попытке выполнить сценарий мы получим:

[me@linuxbox ~]$ trouble

./trouble: строка 9: ошибка синтаксиса около неожиданной лексемы `else'

./trouble: строка 9: `else'

И снова сообщение об ошибке указывает на место, расположенное гораздо дальше фактического места ошибки. Здесь складывается очень интересная ситуация. Как вы помните, if принимает список команд и проверяет код завершения последней команды в списке. В нашей программе мы задумали список с единственной командой [, которая является синонимом команды test. Команда [ принимает все, что следует за ней, как список аргументов – в данном случае четыре аргумента: $number, =, 1 и ]. В отсутствие точки с запятой в список аргументов будет добавлено слово then, что синтаксически допустимо. Следующая команда echo также допустима. Она интерпретируется как еще одна команда в списке команд, которую if должна выполнить и проверить код завершения. Далее следует неуместное здесь слово else, потому что командная оболочка распознает его как зарезервированное слово (слово, имеющее специальное значение для командной оболочки), а не как имя команды. Это объясняет смысл сообщения об ошибке.

Непредвиденная подстановка

Существуют ошибки, которые возникают лишь время от времени. Иногда сценарий работает без ошибок, а иногда терпит неудачу из-за работы механизма подстановки. Для демонстрации этой проблемы вернем точку с запятой на место и изменим значение переменной number, присвоив ей пустое значение:

#!/bin/bash

# trouble: сценарий для демонстрации распространенных видов ошибок

number=

if [ $number = 1 ]; then

        echo "Number is equal to 1."

else

        echo "Number is not equal to 1."

fi

При попытке выполнить сценарий после внесения изменений мы получим:

[me@linuxbox ~]$ trouble

./trouble: строка 7: [: =: ожидается использование унарного оператора

Number is not equal to 1.

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

[ $number = 1 ]

механизмом подстановки, который заменит number пустым значением:

[ = 1 ]

получится недопустимый результат, и командная оболочка сгенерирует сообщение об ошибке. Оператор = является бинарным (он требует наличия двух операндов, по одному с каждой стороны), но первое значение отсутствует, поэтому команда test ожидает встретить унарный оператор (такой, как -z). Далее, поскольку test вернула ненулевой код завершения (из-за ошибки), команда if получит ненулевой код завершения, примет соответствующее решение и выполнит вторую команду echo.

Эту проблему можно исправить, заключив в кавычки первый аргумент команды test:

[ "$number" = 1 ]

Теперь подстановка приведет к следующему результату:

[ "" = 1 ]

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

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

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

• Неправильное условное выражение. Очень легко неправильно запрограммировать оператор if/then/else и получить ошибочную логику работы. Иногда логика получается полностью обратной желаемой или не охватывает весь возможный набор ситуаций.

• Ошибки «смещения на единицу». При программировании циклов со счетчиками можно упустить из виду, что цикл должен начинать считать с 0, а не с 1, чтобы счет закончился в нужной точке. Ошибки этого вида приводят к тому, что цикл выполняет на одну итерацию больше или меньше, заканчиваясь соответственно слишком поздно или слишком рано.

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

Защитное программирование

При программировании важно не опираться на допущения, то есть тщательно проверять коды завершения программ и команд, используемых сценарием. Вот пример из реальной жизни. Системный горе-администратор написал сценарий, выполняющий некую административную задачу на очень важном сервере. Этот сценарий содержал следующие две строки кода:

cd $dir_name

rm *

В самих строках нет никакой ошибки, при условии, что каталог, указанный в переменной dir_name, действительно существует. Но что случится, если это не так? Тогда команда cd потерпит неудачу, сценарий перейдет к следующей строке и удалит файлы в текущем рабочем каталоге. Результат, как вы понимаете, далек от ожидаемого! Несчастный администратор уничтожил массу важных файлов на сервере из-за этой логической ошибки.

Рассмотрим несколько способов усовершенствования описанной логики. Прежде всего, можно поставить вызов команды rm в зависимость от успеха cd:

cd $dir_name && rm *

В этом случае, если команда cd потерпит неудачу, команда rm не будет выполнена. Так намного лучше, но еще остается вероятность отсутствия переменной dir_name или хранения в ней пустого значения, что, безусловно, приведет к удалению файлов в домашнем каталоге пользователя. Этого можно избежать, убедившись, что dir_name действительно содержит имя существующего каталога:

[[ -d $dir_name ]] && cd $dir_name && rm *

В подобных ситуациях, как описанных выше, лучше прервать выполнение сценария с выводом сообщения об ошибке:

if [[ -d $dir_name ]]; then

        if cd $dir_name; then

                rm *

        else

                echo "cannot cd to '$dir_name'" >&2

                exit 1

        fi

else

        echo "no such directory: '$dir_name'" >&2

        exit 1

fi

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

Проверка ввода

Главное правило надежного программирования: если программа принимает ввод, она должна уметь обработать все, что ей передали. Обычно это означает тщательную отбраковку ввода с целью гарантировать, что дальнейшей обработке будут подвергнуты только допустимые данные. Пример такой проверки мы видели в предыдущей главе, когда обсуждали команду read. Там один из сценариев содержал следующую проверку выбранного пункта меню:

[[ $REPLY =~ ^[0-3]$ ]]

удачный дизайн есть функция от времени

Когда я в студенчестве изучал промышленное проектирование, мудрый профессор учил нас, что степень проработки проекта определяется объемом времени, выделенного проектировщику. Если вам дано 5 минут на проектирование устройства для уничтожения воздушных целей, вы спроектируете мухобойку. А если срок – 5 месяцев, вы сможете спроектировать лазерную систему противовоздушной обороны.

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

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

Тестирование

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

Заглушки

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

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

if [[ -d $dir_name ]]; then

        if cd $dir_name; then

                echo rm * # ТЕСТИРОВАНИЕ

        else

                echo "cannot cd to '$dir_name'" >&2

                exit 1

        fi

else

        echo "no such directory: '$dir_name'" >&2

        exit 1

fi

exit # ТЕСТИРОВАНИЕ

Так как проверка ошибочных условий уже выводит содержательные сообщения, нам не требуется добавлять ничего нового. Самое важное изменение заключается в добавлении команды echo перед командой rm, которая выведет ее и список ее аргументов, но не разрешит ей выполниться. Это изменение позволит безопасно выполнить код. В конец фрагмента мы добавили команду exit, чтобы завершить тест и предотвратить выполнение любых других частей сценария. Необходимость этого шага зависит от предназначения сценария.

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

Комплекты тестов

Чтобы извлечь пользу из тестирования, важно создавать и применять качественные комплекты тестов. Для этого следует тщательно подобрать данные для ввода или условия работы, отражающие крайние и пограничные ситуации. В нашем фрагменте кода (который очень прост) мы хотим проверить, как действует код в трех случаях:

• dir_name содержит имя существующего каталога;

• dir_name содержит имя несуществующего каталога;

• dir_name содержит пустое значение.

Проверив каждое из этих условий, мы получим приличный охват тестированием.

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

Отладка

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

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

Поиск проблемной области

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

if [[ -d $dir_name ]]; then

        if cd $dir_name; then

                rm *

        else

                echo "cannot cd to '$dir_name'" >&2

                exit 1

        fi

# else

#         echo «no such directory: '$dir_name'» >&2

#         exit 1

fi

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

Трассировка

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

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

echo «preparing to delete files» >&2

if [[ -d $dir_name ]]; then

        if cd $dir_name; then

echo «deleting files» >&2

                rm *

        else

                echo "cannot cd to '$dir_name'" >&2

                exit 1

        fi

else

        echo "no such directory: '$dir_name'" >&2

        exit 1

fi

echo «file deletion complete» >&2

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

Теперь, запустив сценарий, убедимся, что удаление файлов действительно было выполнено:

[me@linuxbox ~]$ deletion-script

preparing to delete files

deleting files

file deletion complete

[me@linuxbox ~]$

Кроме того, bash поддерживает встроенный метод трассировки, реализованный в виде параметра -x и команды set с параметром -x. Возьмем для примера сценарий trouble, написанный ранее, и активируем встроенный механизм трассировки для всего сценария, добавив параметр -x в первую строку:

#!/bin/bash -x

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

if [ $number = 1 ]; then

        echo "Number is equal to 1."

else

        echo "Number is not equal to 1."

fi

После запуска мы получим следующие результаты:

[me@linuxbox ~]$ trouble

+ number=1

+ '[' 1 = 1 ']'

+ echo 'Number is equal to 1.'

Number is equal to 1.

Включенный механизм трассировки позволяет увидеть, какой вид приобретают команды после применения подстановки. Начальные знаки «плюс» помогают отличить трассировочную информацию от обычного вывода. Знак «плюс» – это символ по умолчанию, используемый для вывода трассировки. Он хранится в переменной командной оболочки PS4 (prompt string 4 – строка приглашения 4). Изменим значение этой переменной, чтобы сделать трассировочный вывод более полезным. Ниже мы изменили эту переменную, включив в трассировочный вывод текущий номер выполняемой строки в сценарии. Обратите внимание на необходимость использования одиночных кавычек – это предотвращает подстановку до момента, когда строка приглашения не будет использоваться фактически:

[me@linuxbox ~]$ export PS4='$LINENO + '

[me@linuxbox ~]$ trouble

5 + number=1

7 + '[' 1 = 1 ']'

8 + echo 'Number is equal to 1.'

Number is equal to 1.

Выполнить трассировку только выбранного фрагмента сценария можно с помощью команды set с параметром -x:

#!/bin/bash

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

set -x # Включить трассировку

if [ $number = 1 ]; then

        echo "Number is equal to 1."

else

    echo "Number is not equal to 1."

fi

set +x # Выключить трассировку

Здесь мы использовали команду set с параметром -x, чтобы включить трассировку, и с параметром +x, чтобы выключить ее. Этот прием используется для исследования сразу нескольких проблемных фрагментов в сценарии.

Исследование значений в процессе выполнения

Часто вместе с трассировкой полезно выводить содержимое переменных, чтобы иметь более полное представление о действиях сценария. Обычно для этого используются дополнительные инструкции echo:

#!/bin/bash

# trouble: сценарий для демонстрации распространенных видов ошибок

number=1

echo «number=$number» # ОТЛАДКА

set -x # Включить трассировку

if [ $number = 1 ]; then

echo "Number is equal to 1."

else

echo "Number is not equal to 1."

fi

set +x # Выключить трассировку

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

Заключительное замечание

В этой главе мы рассмотрели несколько проблем, с которыми можно столкнуться в процессе разработки сценариев. Конечно, таких проблем несоизмеримо больше. Приемы, описанные здесь, помогут вам в поиске наиболее распространенных видов ошибок. Отладка – это искусство предотвращения ошибок (за счет постоянного тестирования в ходе разработки) и их поиска (путем эффективного использования приемов трассировки), которое дается только с опытом.


    Ваша оценка произведения:

Популярные книги за неделю