Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

Использование повторений в программе

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

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

Оболочка Bourne имеет три различные встроенные конструкции циклов. Один из основных моментов при программировании циклов - прерывание цикла. Огромные объемы компьютерного времени напрасно тратятся программами, которые случайно входят в бесконечные циклы. Основное различие между тремя конструкциями циклов оболочки заключается в методе прерывания цикла. Тремя типами циклов являются циклы while, until и for; все они рассматриваются по очереди в последующих разделах.

Повторение в цикле while

Конструкция while позволяет указывать команды, которые будут выполняться, пока некоторое условие истинно.

Общий формат конструкции while следующий:

while command

do

command

command

command

done

Давайте рассмотрим программу squares в листинге 9.6.

Листинг 9.6. Пример цикла while.

# squares - при успешном выполнении печатает квадраты целых чисел

int=l

while [ $int -lt 5 ]

do

sq='expr $int \* $int'

echo $sq

int='expr $int +1' done

echo "Job Complete"

$ squares

1

4

9

16

Job Complete

$

В листинге 9.6 до тех пор, пока значение переменной int меньше пяти, команды внутри цикла выполняются. При пятом повторении условие test, связанное с оператором while, возвращает ненулевое значение, и выполняется команда, следующая за оператором done.

В интерактивной программе архивирования в листинге 9.5 пользователю предоставляется возможность сделать единственный запрос, после чего программа прерывается. Используя оператор while, программу можно изменить, чтобы предоставить пользователю возможность вводить несколько запросов. Измененная программа показана в листинге 9.7.

Листинг 9.7. Измененная интерактивная программа архивирования.

# Интерактивная программа резервного копирования, восстановления

# или выгрузки каталога

echo "Welcome to the menu driven Archive program"

ANSWER=Y

while [ $ANSWER = Y -o $ANSWER = у ]

do

echo _

# Считывание и подтверждение имени каталога

echo "What directory do you want? \c"

read WORKDIR

if [ ! -d $WORKDIR ]

then

echo "Sorry, $WORKDIR is not a directory"

exit 1 fi

# Превращение каталога в текущий рабочий каталог

cd $WORKDIR

# Отображение меню

echo "Make a Choice from the Menu below"

echo _

echo "1 Restore Archive to $WORKDIR"

echo "2 Backup $WORKDIR "

echo "3 Unload $WORKDIR"

echo

# Считывание и выполнение выбора пользователя

echo "Enter Choice: \c"

read CHOICE

case "$CHOICE" in

    1. echo "Restoring..."

cpio -i </dev/rmt0; ;

2) echo "Archiving..."

ls | cpio -o >/dev/rmt0;;

3) echo "Unloading..."

ls | cpio -o >/dev/rmt0;;

*) echo "Sorry, $CHOICE is not a valid choice"

esac

# Проверка ошибок cpio

if [ $? -ne 0 ]

then

echo "A problem has occurred during the process"

if [ $CHOICE = 3 ]

then

echo "The directory will not be erased"

fi

echo "Please check the device and try again" exit 2

else

if [ $CHOICE - 3 ]

then

rm *

fi

fi

echo "Do you want to make another choice? \c"

read ANSWER

done

Путем инициализации переменной ANSWER значением Y, помещения основной части программы внутри цикла while и получения нового значения ANSWER в конце цикла в программе листинга 9.7, пользователь получает возможность оставаться в программе до тех пор, пока не ответит N на вопрос.

Повторение внутри цикла until

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

Общий формат конструкции until выглядит следующим образом:

until command

do

command

command

command

done

Можно было бы изменить интерактивную программу архивирования просто заменив while на until:

until [ $ANSWER = N -о $ANSWER - n ]

Обработка произвольного числа параметров с помощью команды shift

Перед рассмотрением цикла for имеет смысл рассмотреть команду shift, поскольку цикл for в действительности является ускоренным использованием команды shift.

В предыдущих примерах аргументы командной строки либо считались одиночными, либо передавались команде как единое целое с помощью переменной $*. Если программе требуется обработка каждого аргумента командной строки в отдельности, а количество аргументов неизвестно, можно обрабатывать их по одному, используя команду shift. Эта команда выполняет сдвиг аргументов на одну позицию; $2 становится параметром $1, $3-$2 и т.д. Параметр, который был параметром $1 до выполнения команды shift, после ее выполнения недоступен. Следующая простая программа демонстрирует эту концепцию:

# shifter

until [ $# -eq 0 ]

do

echo "Argument is $1 and "expr $# - 1" argument (s) remain"

shift done

$ shifter 1234

Argument is 1 and 3 argument (s) remain

Argument is 2 and 2 argument (s) remain

Argument is 3 and 1 argument(s) remain

Argument is 4 and 0 argument (s) remain

$

В этом примере можно было заметить, что при каждом выполнении команды shift переменная #$ уменьшалась. Зная это, можно использовать цикл until для обработки всех переменных. Давайте рассмотрим пример, приведенный в листинге 9.8 - программу суммирования списка целых чисел, вводимых в виде аргументов командной строки.

Листинг 9.8. Программа суммирования целых чисел.

# sumints - программа суммирования ряда целых значений

#

if [ $# -eq 0 ]

then

echo "Usage: sumints integer list"

exit 1

fi

sum=0

until [ $# -eq 0 ]

do

sum='expr $sum + $1'

shift done

echo $sum

Following is the execution of sumints:

$ sumints 12 18 6 21

57

Команду shift можно также использовать для другой цели. Оболочка Bourne предопределяет девять позиционных параметров $1-$9. Это не означает, что только девять позиционных параметров могут быть введены в командной строке, но для доступа к позиционным параметрам, кроме первых девяти, необходимо использовать команду shift.

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

shift 3

Повторение с помощью цикла for

Третий тип конструкции цикла в оболочке Boume - цикл for. Этот цикл отличается от других конструкций тем, что не основывается на условии, которое может принимать значение true (истинно) или false (ложно). Вместо этого цикл for выполняется по одному разу для каждого слова из приведенного в операторе списка. При каждом повторении цикла переменная, передаваемая командной строке цикла for, принимает значение следующего слова в списке аргументов. Общий синтаксис цикла for выглядит следующим образом:

for variable in argi arg2 .. . argn do

command

command

done

Следующий простой пример иллюстрирует конструкцию:

$ for LETTER in a b с d; do echo $LETTER; done

a

b

c

d

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

Программу sumints, приведенную в листинге 9.8, можно было бы также написать, используя цикл for, передав ему аргументы командной строки. Измененная программа показана в листинге 9.9.

Листинг 9.9. Измененная программа суммирования целых значений.

# sumints - программа суммирования ряда целых значений

#

if [ $# -eq 0 ]

then

echo "Usage: sumints integer list"

exit 1

fi

sum=0

for INT in $*

do

sum='expr $sum + $INT'

done

echo $sum

Выход из тела цикла

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

Иногда в программировании оболочки используется технология, при которой запускается бесконечный цикл, т.е. цикл, который не закончится до тех пор, пока не будет выполнена команда break или continue. Такой цикл обычно начинается с команды true (истинно) или false (ложно). Команда true всегда возвращает состояние выхода равное нулю, а команда false - ненулевое состояние выхода. Цикл

while true

do

command

command

done

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

until false

do

command

command

done

Эта технология была использована для некоторого упрощения использования интерактивной программы архивирования, представленной в листинге 9.7. Измененная программа показана в листинге 9.10.

Листинг 9.10. Еще одна версия интерактивного архиватора.

# Интерактивная программа резервного копирования, восстановления или выгрузки

# каталога

echo "Welcome to the menu driven Archive program"

while true

do

# Отображение меню

echo

echo "Make a Choice from the Menu below"

echo _

echo "1 Restore Archive"

echo "2 Backup directory"

echo "3 Unload directory"

echo "4 Quit"

echo

# Считывание выбора пользователя

echo "Enter Choice: \c"

read CHOICE

case $CHOICE in

[1-3] ) echo _

# Считывание и подтверждение имени каталога

echo "What directory do you want? \c"

read WORKDIR

if [ ! -d "$WORKDIR" ]

then

echo "Sorry, $WORKDIR is not a directory"

continue

fi

# Превращение каталога в текущий рабочий каталог

cd $WORKDIR;;

4) :;;

*) echo "Sorry, $CHOICE is not a valid choice"

continue __

esac

case "$CHOICE" in

  1. echo "Restoring..."

cpio -i </dev/rmt0;;

2) echo "Archiving..."

ls | cpio -o >/dev/rmt0;;

3) echo "Unloading..."

ls | cpio -o >/dev/rmt0;;

    1. echo "Quitting"

break;;

esac

# Проверка ошибок cpio

if [ $? -ne 0 ]

then

echo "A problem has occurred during the process"

if [ $CHOICE = 3 ]

then

echo "The directory will not be erased"

fi

echo "Please check the device and try again"

continue

else

if [ $CHOICE = 3 ]

then

rm *

fi

fi

done

В программе листинга 9.10 цикл продолжается до тех пор, пока команда true возвращает нулевое состояние выхода, что имеет место всегда, или пока пользователь не выберет четыре, что приводит к выполнению команды break и прерывает цикл. Обратите также внимание, что, если пользователь допускает ошибку в выборе или в вводе имени каталога, выполняется оператор continue, а не оператор exit. Таким образом, пользователь может оставаться в программе, даже если допускает ошибку при вводе данных, но ошибочные данные не могут быть обработаны.

Обратите также внимание на использование двух операторов case. Первый из них запрашивает ввод имени каталога пользователем только при выборе опций 1, 2 или 3. Этот пример иллюстрирует схожесть соответствия шаблону в операторе case и в командной строке. В первом операторе case, если пользователь выбирает опцию 4, выполняется нулевая команда (:). Поскольку первый оператор case осуществляет проверку на предмет недопустимого выбора и выполняет в таком случае оператор continue, второй оператор case должен выполнять проверку только допустимых вариантов выбора.

Структурное программирование оболочки с использованием функций

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

Общий синтаксис определения функции следующий:

funcname ()

{

command

command

}

Будучи определенной, функция может быть вызвана из любого места внутри оболочки посредством использования funcname в качестве команды. Существуют две причины для группирования команд в функцию. Одна - необходимость разбиения сложной программы на более управляемые сегменты путем создания структурированной программы. Структурированная программа может иметь следующую форму:

# запуск программы

setup ()

{ command list ; }_

do_data ()

{ command list ; }_

cleanup ()

{ command list ; }_

errors ()

{ command list;}_

setup

do_data

cleanup

# завершение программы

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

СОВЕТ

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

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

icontinue ()

{

while true

do

echo "Continue? (y/n) \c"

read ANSWER

case $ANSWER in

[Yy] ) return 0;;

[Nn] ) return 1;;

* ) echo "Answer у or n";;

esac done }

Теперь в программе операторы continue можно заменить функцией icontinue.

If icontinue then continue else break fi

Теперь отображение приглашения, считывание и проверка ошибок выполняется функцией icontinue вместо повторения этих команд в каждой точке continue. Этот пример также иллюстрирует способность функции возвращать состояние выхода с помощью команды return. Если в функции отсутствуют команды return, состояние выхода функции - это состояние выхода последней в этой функции команды.

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

$ dir() {ls -l;}_

$ dir

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

$ unset dir

Функции также могут получать аргументы, как показано в следующем примере:

$ dir () {_

> echo "Permission ln Owner Group File Sz Last Acces"

> echo "------------- -- --------- ------- ----- -- ----- --------"

> ls - l $*;

> }

$ dir L*

В этом примере аргумент L* был передан функции dir и подставлен в команде ls вместо $*. Как правило, сценарий оболочки выполняется в субоболочке. Любые изменения, выполненные в переменных в субоболочке, не оказывают влияния на родительскую оболочку. Команда точки (.) вызывает считывание и выполнение сценария оболочки внутри текущей оболочки. Любые определения функций или присвоения переменных осуществляются в текущей оболочке. Обычно команда точки используется для реинициализации переменных среды посредством повторного считывания файла .profile. Для получения более подробной информации о файле .profile обратитесь к разделу "Настройка оболочки" ниже в этой главе.

$ . .profile

Борьба с неожиданностями с помощью команды trap

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

Однако иногда может потребоваться выполнение каких-либо специальных действий при получении сигнала. Если программа создает временные файлы данных и прерывается сигналом, временные файлы остаются. В оболочке Bourne действие программы по умолчанию при получении сигнала можно изменить, используя команду trap (перехват).

Общий формат этой команды следующий:

trap command_string signals

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

Ниже приводятся три наиболее часто встречающиеся сигналы, которые потребуется перехватывать:

Сигнал Описание

1 Зависание

2 Прерывание оператором

15 Программное прерывание (сигнал kill)

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

Предположим, что имеется программа, создающая некоторые временные файлы. Когда программа завершается нормально, временные файлы удаляются, но получение сигнала приводит к немедленному прерыванию программы, в результате чего на диске могут остаться временные файлы. Используя команду trap, можно вызвать удаление временных файлов, даже если программа прерывается не нормальным образом, ввиду получения сигнала зависания, прерывания или kill:

trap "rm $TEMPDIR/*$$; exit" 1 2 15

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

После подстановки результирующая командная строка хранится в таблице перехватов. Если команду trap изменить для использования одинарных кавычек

trap 'rm $TEMPDIR/*$$; exit' 1 2 15

то при ее выполнении никакая подстановка переменных не происходит, и командная строка

rm $TEMPDIR/*$$; exit

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

Командная строка команды trap почти всегда содержит оператор exit. Если не включать его в эту команду, то при обнаружении сигнала выполняется команда rm, и программа продолжает выполнение с того места, где она была прервана в момент прихода сигнала. Иногда это требуется вместо выхода. Например, если требуется, чтобы программа не останавливалась при отключении терминала, можно перехватить сигнал зависания, указав команду null, как показано в следующем примере:

trap : 1

Можно установить перехват обратно в состояние по умолчанию, выполнив команду trap без командной строки:

trap 1

Следующая команда заставляет пользователя для прерывания программы дважды нажать клавишу прерывания:

trap ' trap 2 ' 2

Условное выполнение команд с помощью конструкций и/или

Как уже было показано, часто можно написать программу оболочки несколькими способами, получив при этом одни и те же результаты. Например, оператор until - просто противоположный способ использования оператора while. Можно вызвать условное выполнение команд, используя конструкцию if-then-else, но этого же можно добиться, используя операторы && и ||. В языке программирования С эти символы представляют логические операции and (и) и ог (или), соответственно. В оболочке Bourne && соединяет две команды таким образом, что вторая команда выполняется только в том случае, если первая успешна.

Общий формат операции && следующий:

command && command

Например, в операторе

rm $TEMPDIR/* && echo "Files successfully removed"

команда echo выполняется только в том случае, если команда гю успешна. Этот же результат можно было бы получить с помощью оператора if-then, подобного следующему:

if rm $TEMPDIR/*

then

echo "Files successfully removed"

fi

И наоборот, || соединяет две команды так, что вторая выполняется только если первая не была успешной:

rm $TEMPDIR/* | | echo "Files were not removed"

Эта строка эквивалентна конструкции

if rm $TEMPDIR/*

then

:

else

echo "Files were not removed"

fi

Эти операции можно также объединять. В следующей командной строке команда commands выполняется только тогда, когда и command 1 и command2 успешны:

commandl && command2 && commands

Можно также объединять операции различных типов. В следующей командной строке команда commands выполняется только в том случае, если команда command1 успешна, а команда command2 - не успешна:

commandl command2 | | command3

Операции && и || - простые формы условного выполнения команд, которые обычно используются только в тех случаях, когда одиночные команды не подлежат выполнению. Хотя команды могут быть составными, если в этом формате представлено много команд, программу может быть трудно читать. В целом, в случае использовании более одной-двух команд, конструкции if-then кажутся более понятными.

Считывание опций, оформленных в стиле UNIX

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

команда -опции параметры

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

getopts option_ string variable

где option_string содержит допустимые односимвольные опции. Если getopts встречает дефис (-) в потоке ввода команды, она сравнивает следующий за дефисом символ с символами в opfion_strmg. Если обнаруживается соответствие, getopts присваивает переменной variable значение опции; если следующий за дефисом символ не совпадает ни с одним символом в option_string, переменной variable присваивается значение знака вопроса (?). Если команда getopts больше не видит символов, следующих за дефисом, она возвращает ненулевое состояние выхода. Эта особенность позволяет использовать команду getopts в цикле. Программа, приведенная в листинге 9.11, иллюстрирует использование команды getopts для обработки опций команды date. Программа создает версию команды date, которая соответствует стандартному стилю UNIX, и добавляет несколько опций.

Листинг 9.11. Стандартизованная функция date - newdate.

#newdate

if [ $# -lt 1 ]

then

date

else

while getopts mdyDHMSTjJwahr OPTION

do

case $OPTION in

m) date '+%т Т;; Month of Year

d) date *+%d ';; Day of Month

y) date *+%y ';; Year

D) date *+%D ';; MM/DD/YY

H) date *+%Н ';; Hour

M) date ' +%M ';; Minute

S) date '+%3 ';; Second

T) date '+%t ';; hh:mm:ss

j) date * +%j ';; # day of year

J) date '+%y%j ';; # digit Julian date

w) date '+%w ';; #Day of the week

a) date '+%a ';; # Day abbreveation

h) date '+%h ';; #Month abbreveation

\?) echo "Invalid option $OPTION";;

esac

done

fi

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

Следующие примеры иллюстрирует работу функции newdate:

$ newdate -J

94031

$ newdate -a -h-d

Mon

Jan

31

$ newdate -and

Mon

Jan

31

$

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

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

Листинг 9.12. Программа duplicate.

___ __ __

# Синтаксис: duplicate [-с integer] [-v] filename

# где integer - число копий дубликатов, а

# -v - опция многословного режима

COPIES=1

VERBOSE=N

while getopts vc: OPTION

do

case $OPTION in

c) COPIES=$OPTARG; ;

v) VERBOSE-Y; ;

\?) echo "Illegal Option-exit 1;;

esac

done

if [ $OPTIND -gt $* ]

then

echo "No file name specified"

exit 2

fi

shift 'expr $OPTIHD -1'

FILE=$1

COPY=0

while [ $COPIES -gt $COPY ]

do

COPY='expr $COPY + 1'

cp $FILE ${FILE}${COPY}

if [ VERBOSE - Y ]

then

echo ${FILE)${COPY}

fi done

В программе листинга 9.12, предоставляющей пользователю возможности ввода опций, возникает уникальная проблема: при написании программы не известно, который из аргументов будет содержать имя файла, подлежащего копированию. Команда getopts помогает решить эту проблему, сохраняя номер следующего аргумента в переменной OPTIND. Когда команда getopts обнаруживает все опции, в программе duplicate выполняется проверка переменной OPTIND для выяснения того, указано ли имя файла, а затем команда shift делает имя файла первым аргументом.

$ duplicate -v fileA

fileA1

$ duplicate -с 3 -v fileB

fileB1

fileB2

fileB3

Настройка оболочки

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

Настройка оболочки с помощью переменных среды

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

Добавление разделителей командной строки с помощью переменной среды IFS

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

$ IFS=':'

$ echo:Hello:My:Friend

Hello My Friend

$

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

Проверка нескольких почтовых ящиков с помощью MAILPATH

Большинство пользователей имеют для своей электронной почты только по одному почтовому ящику. Однако некоторым пользователям может потребоваться несколько почтовых ящиков (см. главу 7, в которой описывается электронная почта). Например, Дэйву необходимо прочитывать почту, адресованную лично ему (поступающую на его личный бюджет пользователя), адресованную sysadm (поступающую на его бюджет системного администратора) и адресованную root (поступающую на его основной бюджет), но он может войти в систему, используя лишь один из этих бюджетов. Поэтому Дэйву может потребоваться заставить текущую оболочку проверять все три почтовых ящика, установив переменную среды MAILPATH следующим образом:

$ MAILPATH="/usr/spool/mail/Dave:/usr/spool/mail/sysadm\

:/usr/spool/mail/root"

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

you have mail (на ваше имя поступила почта)

Единственная проблема заключается в том, что Дэйв не знает, какой почтовый ящик следует проверить, получив это сообщение. Можно помочь Дэйву решить эту проблему, изменив сообщение, связанное с каждым почтовым ящиком. Имя почтового ящика в MAILPATH ограничивается знаком процента (%) и выводится сообщение, подобное следующему:

$ MAILPATH="/usr/spool/mail/Dave%Dave has mail\

:/usr/spool/mail/sysadm%sysadm has mail\

:/usr/spool/mail/root%root has mail

Добавление собственных команд и функций

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

Листинг 9.13. Программа смены каталога chdir.

# Программа смены каталога и приглашения

# Синтаксис: chdir каталог

if [ ! -d "$1" ] then

echo "$1 is not a directory"

exit 1

fi

cd $1

PS1="`pwd`>"

export PS1

При выполнении следующей команды chdir из листинга 9.13 ничего не происходит.

$ chdir /usr/home/teresa

$

Не отображается никакое сообщение об ошибке, но приглашение командной строки остается неизменным. Проблема в том, что chdir выполняется в субоболочке, а экспортированная переменная PS1 доступна только для оболочек более низкого уровня. Чтобы chdir работала требуемым образом, она должна выполняться внутри текущей оболочки. Наилучший способ добиться этого - сделать ее функцией. Можно записать функцию в файл .profile, но существует лучшее решение. Сгруппируйте персональные функции в единый файл и загрузите в текущую оболочку, воспользовавшись командой передачи (.). Перепишите chdir как функцию, заменив exit на return. Файл определения функции persfiincs показан в листинге 9.14.

Листинг 9.14. файл персональных функций с chdir, переписанной в качестве функции

# Файл персональной функции persfuncs

chdir ()

{

# Программа смены каталога и приглашения

# Синтаксис: chdir каталог

if [ ! -d -$1" ] then

echo "$1 is not a directory"

return

fi

cd $1

PS1="`pwd'> "

export PS1;

}

$ . persfuncs

$ chdir /usr/home/teresa

/usr/home/teresa> chdir /usr/home/john

/usr/home/john> _

Хранение персональных функций в отдельном файле упрощает их поддержку и отладку по сравнению с хранением в файле .profile.

Можно сделать персональные функции постоянной частью среды, поместив команду

.persfuncs в файл .profile.

Специализированные вопросы

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

Отладка программ оболочки

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

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

Листинг 9.15. Программа суммирования целых значений.

# sumints - программа суммирования ряда целых чисел

#

if [ $# -eq 0 ]

then

echo "Usage: sumints integer list"

exit 1 fi

sum=0

until [ $# -eq 0 ]

do

sum='expr $sum + $1'

shift done

echo $sum

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

$ sh ~x sumints 234

+ [ 3 -eq 0 ]

+ sum-0

+ [ 3 -eq 0 ] ,

+ expr 0+2

+ sum" 2

+ shift

+ [ 2 -eq 0 ]

+ expr 2+3

+ sum= 5

+ shift

+ [ 1 -eq 0 ]

+ expr 5+4

+ sum= 9

+ [ 0 -eq 0 ]

+ echo 9

9

$

Трассировка отображает каждую выполняемую команду и значение всех подстановок, которые были сделаны перед ее выполнением. Обратите внимание, что управляющие слова if, then и until не отображаются.

Группирование команд

Команды оболочки могут группироваться для выполнения в качестве отдельного модуля. Если команды заключаются в скобки, то они выполняются в субоболочке; если они группируются с помощью фигурных скобок ((}), то выполняются в текущей оболочке. Различие заключается во влиянии на переменные оболочки. Команды, выполняемые в субоболочке, не оказывают влияния на переменные в текущей оболочке, если же команды группируются и выполняются в текущей оболочке, то все изменения переменных в группе выполняются по отношению к переменным в текущей оболочке.

$ NUMBER=2

$ (А=2; В=2; NUMBER='expr $A + $В'; echo $Number)

4

$ echo $NUMBER

2

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

$ {А=2; В-2; NUMBER='expr $А + $В'; echo $Number}

4

$ echo $NUMBER

4

Обратите внимание, что второй пример несколько напоминает определение функции. Функция - это именованная группа команд, выполняющаяся в текущей оболочке.

Использование диспетчера слоев оболочки shl

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

Слой создается и именуется диспетчером shl. Пока пользователь работает в слое, он может активизировать диспетчер слоев, воспользовавшись специальным символом (в некоторых системах это Ctrl-Z). Диспетчер слоев оболочки обладает специальным приглашением командной строки (>>>), чтобы его можно было отличить от слоев. Находясь в среде диспетчера слоев оболочки, пользователь может создавать, активизировать и удалять слои. Команды shl приводятся ниже:

  • aa

  • create name Создает слой, названный name.

  • delete name Удаляет слой, названный name,

  • block name Блокирует вывод из слоя name.

  • unblock name Удаляет блокировку вывода для слоя name.

  • resume name Делает слой name активным

  • Toggle Осуществляет переключение на недавний слой.

  • Name Делает слой name активным.

  • layers [-l] name р Для каждого name в этом списке отображает идентификатор процесса. Опция -1 обеспечивает более подробный вывод.

  • help Отображает справку по командам shl.

  • quit Осуществляет выход из shl и всех активных слоев.

Резюме

В этой главе были представлены основополагающие аспекты оболочки Bourne, использование переменных оболочки и основы создания сценариев оболочки. Эти знания пригодятся при изучении других глав, посвященных оболочкам (глав 11 и 12), а также при изучении других языков создания сценариев. Для большинства операционных систем доступно несколько языков и какие-либо возможности создания сценариев системных команд. Однако лишь немногие из них обладают возможностями языка командных сценариев оболочки UNIX. Для написания программ оболочки на таких языках программирования, как С, COBOL, BASIC и т.п. могут потребоваться бесконечные часы программирования. Опыт создания сценариев оболочки UNIX - значительное преимущество для любого системного администратора или программиста. Поэтому настоятельно рекомендуем приобрести дополнительные знания и опыт по созданию сценариев оболочки.
Back

К оглавлению