Linux и UNIX: программирование в shell. Руководство разработчика.

Тейнсли Дэвид

Часть 4

 

 

Основы shell–программирования

 

ГЛАВА 16

 

Понятие о shell–сценарии

В shell–сценарий может включаться одна или несколько команд; здесь нет общепринятых правил. Зачем же создавать целый сценарий ради двух–трех команд? Все зависит от предпочтений пользователя.

В этой главе рассматриваются следующие темы:

   • цель создания shell–сценариев;

   • основные компоненты shell–сценария;

   • способы запуска shell–сценария.

 

16.1. Зачем создаются shell–сценарии

 

Применение shell–сценариев позволяет экономно расходовать рабочее время при выполнении важных и сложных заданий. В конце концов, почему бы для выполнения определенного задания не использовать листинг команд? Затем можно только просмотреть данные вывода. Если результаты удовлетворительные, можно переходить к выполнению следующего задания. Если же результаты не устраивают вас, следует внимательно изучить листинг. Shell–сценарии производят сортировку файлов, вставку текста в файлы, перемещение файлов, удаление строк из файлов, а также удаление старых файлов из системы. Shell–сценарии также выполняют в системе некоторые административные задания. В этих сценариях используются переменные, условные, арифметические и циклические конструкции, которые можно отнести к системным командам. За счет этих возможностей сценарии создаются довольно быстро. Интерпретатор команд может применять в качестве вводных данных для одной команды информацию, полученную при выполнении другой команды. Чтобы shell–сценарий применялся в различных системах UNIX и Linux, в него нужно внести лишь небольшие изменения. Это связано с тем, что интерпретатор shell обладает высокой степенью универсальности. Определенные трудности возникают лишь вследствие ориентации системных команд на определенные системы.

 

16.1.1. Не отказывайтесь от новых идей

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

 

16.2. Структура сценария

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

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

#!/bin/sh

Система получает указание, где следует искать интерпретатор Bourne shell.

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

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

 

16.3. Выполнение сценария

Ниже рассматривается пример, который уже обсуждался ранее. В данном случае, файл называется cleanup.

$ pg cleanup

#!/bin/sh

#имя: cleanup

#это общий сценарий, выполняющий очистку echo "starting cleanup…wait"

rm /usr/local/apps/log/*.log

tail -40 /var/adm/messages /tmp/messages

rm /var/adm/messages

mv /tmp/messages /var/adm/messages

echo "finished cleanup"

Приведенный выше сценарий отменяет отображение сообщений /var/adm/ путем усечения файла сообщений. В задачи этого сценария также входит удаление всех журнальных файлов в каталоге /usr/local/apps/log.

Для выполнения сценария применим команду chmod:

$ chmod u+x cleanup

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

$ cleanup

При отображении сообщения об ошибке, например:

$ cleanup

sh: cleanup: command not found

воспользуйтесь командой: $ ./cleanup

Если перед выполнением сценария нужно указать путь доступа к нему или же сценарий сообщает, что не может обнаружить команду, достаточно в значение переменной path из файла .profile добавить каталог bin. При вводе следующей информации сначала убедитесь, что вы находитесь в каталоге $HOME/bin:

$ pwd

$ /home/dave/bin

Если последняя часть команды pwd включает название подкаталога /bin, его следует использовать при указании имени пути. Измените файл .profile и добавьте в файл .profile каталог $HOME/bin:

PATH=$PATH:$HOME/bin

В случае если подкаталог /bin отсутствует, создайте его; сначала удостоверьтесь, что находитесь в начальном каталоге.

$ cd $HOME

$ mkdir bin

После этого добавьте каталог bin в переменную PATH в файле .profile, затем заново инициализируйте файл .profile

$ . .profile

Теперь все должно получиться.

Если же проблемы остались, просмотрите главы 2 и 13. В этих главах содержатся сведения о командах find и xargs, а также о настройках переменных среды, которые окажутся полезными при создании и выполнении сценариев.

Все листинги книги являются полными. Чтобы запустить на выполнение сценарии, введите в файл команды, сохраните его на диске и выйдите из текстового редактора. Затем воспользуйтесь командой chmod для установки права выполнения. Теперь можно переходить к делу.

 

16.4. Заключение

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

 

ГЛАВА 17

 

Проверка условий

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

В главе рассматриваются следующие темы:

   • применение команды test для обработки файлов, строк и чисел;

   • использование команды expr при проверке численных значений и строк. Команда expr выполняет проверку и вывод численных данных. Команды test

и expr с помощью кода завершения последней команды $? выводят на экран значение 0, если заданное условие выполняется, и 1, если условие не выполняется.

 

17.1. Проверка прав доступа к файлу

Основные форматы команды test:

test условие

или

[ условие ]

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

Таблица 17.1. Проверка прав доступа к файлу

-d Каталог
-f Обычный файл
-L Символическая связь
-r Файл для чтения
-s Файл имеет ненулевой размер, он не пуст
-w Файл для записей
-u Файл имеет установленный бит suid
-x Исполняемый файл

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

$ ls -l scores.txt

-rw‑r—г -l dave admin 0 May 15 11:29 scores.txt

$ [ -w scores.txt ]

$ echo $?

0

$ test -w scores.txt

$ echo $?

0

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

$ [ -х scores.txt ]

$ echo $?

1

Нет, не является, что и следовало ожидать после изучения списка прав доступа для файла scores.txt.

В следующем примере проверяется существование каталога appsbin.

drwxr‑xr‑x 2 dave admin 1024 May 15 15:53 appsbin

$ [ -d appsbin ]

$ echo $?

0

Ответ утвердительный, каталог appsbin присутствует. Чтобы проверить, установлен ли бит suid, примените:

-rwsr‑x--- 1 root root 28 Apr 30 13:12 xab

$ [ -u xab ]

$ echo $?

0

Из этого примера следует, что бит suid установлен.

 

17.2. Применение логических операторов при осуществлении проверки

Итак, проверка прав доступа к файлу была осуществлена, но иногда возникает необходимость в сравнении различных прав доступа. Чтобы реализовать подобную проверку интерпретатор shell предлагает три типа логических операторов;

-a Логическое AND, возвращает истину, если обе части оператора принимают истинное значение
-o Логическое OR, возвращает истину, если какая‑либо из частей оператора может принимать истинное значение
! Логическое NOT, возвращает истину, если условие ложно

А теперь выполним сравнение следующих файлов:

-rw‑r--r-- 1 root root 0 May 15 11:29 scores.txt

-rwxr‑xr—- 1 root root 0 May 15 11:49 results.txt

В следующем примере проверяется, установлены ли для файлов права чтения.

$ [ -w results.txt -a -w scores.txt ]

$ echo $?

0

Ответ звучит утвердительно.

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

$ [ -х results.txt -о -x scores.txt ]

$ echo $?

0

Файл scores.txt не является исполняемым, а файл results.txt — исполняемый. Чтобы проверить, установлены ли для файла results.txt права записи и выполнения, примените следующую команду:

$ [ -w results.txt -a -x results.txt ]

$ echo $?

0

В данном случае получается утвердительный ответ.

 

17.3. Проверка строк

Проверка строк является важным этапом при отслеживании ошибок. Значение этого этапа повышается, если проверяются вводимые пользователями данные либо выполняется сравнение переменных. Чтобы проверить строки, достаточно выбрать один из пяти форматов.

test "строка"

test оператор_строки "строка"

test "строка" оператор_строки "строка"

[ оператор_строки строка ]

[ строка оператор_строки строка ]

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

== Две строки равны

!= Две строки не равны

-z Эта строка нулевая

-n Эта строка не является нулевой

Чтобы проверить, присвоено ли переменной среды editor нулевое значение, примените команду:

$ [ -х $EDITOR ]

$ echo $?

1

Ответ отрицательный. Присвоено ли этой переменной значение vi?

$ [ $EDITOR= "vi" ]

$ echo $?

0

Ответ утвердительный. Отобразим это значение на экране:

$ echo $EDITOR

vi

Проверить, равны ли значения переменных таре и таре2, можно, воспользовавшись следующей командой:

$ TAPE="/dev/rmt0"

$ TAPE2="/dev/rmt1"

$ [ "$TAPE" = "$ТАРЕ2" ]

$ echo $?

1

Ответ отрицательный. При присваивании переменных необязательно применять двойные кавычки. Но при сравнении строк использование двойных кавычек является непременным условием.

Чтобы проверить "неравенство" переменных таре и таре2, примените следующую команду:

$ [ »$TAPE" != "$ТАРЕ2" ]

$ echo $?

0

Значения этих переменных не равны.

 

17.4. Проверка чисел

Для сравнения чисел можно воспользоваться операторами другого рода. Общий формат:

"число" числовой_оператор "число" или

[ "число" числовой_оператор "число" ]

где в качестве выражения числовой_оператор могут фигурировать следующие операторы:

-eq Два числа равны
-ne Два числа не равны
-gt Первое число больше второго числа
-lt Первое число меньше второго числа
-le Первое число меньше или равно второму числу
-gt Первое число больше или равно второму числу

Выясним, равно ли одно число другому (в данном случае рассматривается, равно ли число 130 числу 130):

$ NUMBER=130

$ [ "$NUMBER" -eq "130" ]

$ echo $?

0

Превосходно! Результатом сравнения явилось значение "истина". Теперь изменим второе число и проверим, отобразится ли сообщение об ошибке. Возвращается значение 1 (130 не равно 100(.

$ [ "$NUMBER" —eq "100" ] $ echo $?

1

Чтобы проверить, является ли число 130 больше числа 100, воспользуйтесь следующей командой:

$ [ "$NUMBER" —gt "100" ]

$ echo $?

0

Ответ утвердительный.

Так же можно проверять два целых значения переменных. Ниже уточняется, будет ли значение переменной DEST_COUNT большим, чем значение переменной SOURCE_COUNT.

$ SOURCE_COUNT=13

$ DEST_COUNT-15

$ [ "$DEST_COUHT" -gt "$SOURCE_COUNT" ]

$ echo $?

0

Необязательно для выполнения проверки обращаться к переменной: можно сравнивать и числа, но в этом случае следует применять кавычки:

$ [ "990" -le "995" ] $ echo $?

0

Можно также комбинировать и тестировать выражения с помощью логических операторов. При этом следует пользоваться только одной парой квадратных скобок — не применяйте две пары скобок. Если не учитывать этого замечания, отобразится сообщение об ошибке — "too many arguments" (слишком много аргументов):

$ [ "990" -le "995" ] —а [ "123" —gt "33" ]

sh:[: too many arguments

В следующем примере проверяются два выражения. Если оба выражения истинны, результат будет истинным. Ниже приводится корректный метод выполнения проверки.

$ [ "990" -le "995" -a "123" -gt "33" ] $ echo $?

0

 

17.5. Применение команды expr

 

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

expr аргумент оператор аргумент

Команду expr удобно использовать для подсчета количества строк:

$ expr 10 + 10

20

$ expr 900 + 600

1500

$ expr 30/3

10

$ expr 30/3/2

5

В случае применения знака умножения, можно с помощью обратной косой черты отменить его значение- Если этого не сделать, интерпретатор команд может неверно определить значение символа *:

$ expr 30 \*.3

90

 

17.5.1. Приращение переменной цикла

Команда expr выполняет приращение переменной цикла. Сначала переменной цикла присваивается начальное значение нуль. Затем добавляется единица. Кавычки применяются для обозначения подстановки команд. Выводимые данные, полученные с помощью команды expr, присваиваются переменной цикла.

$ loop=0

$ LOOP=`expr $LOOP + 1`

 

17.5.2. Проверка численных значений

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

$ expr rr + 1

expr: нечисловой аргумент

Итак, необходимо передать значение переменной (не важно, какой именно), выполнить любую арифметическую операцию и направить выводимые данные в /dev/null. Затем достаточно проверить код завершения последней команды. Если код равен нулю, тогда мы имеем дело с числом; любое другое значение свидетельствует о том, что данное значение не является числом.

$ VALUE=12

$ expr $VALUE + 10 > /dev/null 2>&1

$ echo $?

0

Это — число.

$ VALUE=hello

$ expr $VALUE + 10 > /dev/null 2>&1

$ echo $?

2

А это — не численное значение.

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

$ VALUE=hello

$ expr $VALUE="hello"

1

$ echo $?

0

Команда expr возвращает единицу. Не беспокойтесь, все идет хорошо. Теперь выполним проверку с помощью кода завершения последней команды: возвращается значение нуль. В ходе проверки выяснилось, что строка "hello" действительно равна строке "hello".

 

17.5.3. Поиск по шаблону

С помощью команды expr можно реализовать поиск по шаблону. Подсчет количества символов строки возможен с помощью команды expr. При этом нужно дополнительно указать опцию после двоеточия. Комбинация '. *' означает, что в кавычках может указываться любое количество произвольных символов.

$ VALUE=accounts.doc

$ expr $VALUE : October 8, '.*'

12

Команду expr можно также использовать при поиске совпадающих строк; ниже показано, как применяется шаблон ".doc" для извлечения оставшейся части имени файла.

$ expr $VALUE : '\(.*\).doc'

accounts

 

17.6. Заключение

В настоящей главе рассматриваются основные возможности команд test и expr. Показано, как проверяются права доступа к файлу, как тестируются строки. С помощью других условных операторов, типа "if then else" и "case", можно реализовать всестороннюю проверку. Подобный подход позволяет предпринимать определенные действия по результатам проверки.

 

ГЛАВА 18

 

Управляющие конструкции

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

   • коды завершения;

   • циклы while, for и until;

   • операторы if then else;

   • принятие решений в сценариях;

   • создание меню.

 

18.1. Коды завершения

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

$ echo $?

Существует четыре основных типа кодов завершения. Два из них уже упоминались, а именно: код завершения последней команды $? и команды, изменяющие ход выполнения сценария (&&,||). Две оставшиеся разновидности команд имеют отношение к завершению shell–сценария или интерпретатора shell, а также связаны с кодом завершения или кодами возврата функции. Эти коды рассматриваются в главе 19, посвященной изучению функций.

Для завершения текущего процесса в интерпретаторе shell используется команда exit. Общий формат команды:

exit n

где n — числовое значение.

Чтобы завершить работу с интерпретатором shell, не создавая во время текущего сеанса другой интерпретатор shell, достаточно в командной строке ввести команду exit. Если указать команду exit без параметров, интерпретатор shell будет отображать (и отображает) значение последней команды. Существует большое количество

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

Код завершения 0 Успешное завершение, ошибок нет Код завершения 1 Неудачное завершение, имеется ошибка

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

Примечание:

Начиная с этого места книги, все сценарии включают строки комментария. Строки комментария содержат разъяснения, которые помогают запомнить или ясно представить выполнение сценариев. Поскольку интерпретатор команд игнорирует строки комментария, можно располагать их по своему усмотрению. Строка комментария должна начинаться символом #.

 

18.2. Управляющие конструкции

 

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

#!/bin/sh

# создание каталога

mkdir /home/dave/mydocs

# копирование всех файлов с расширением doc

cp *.docs /home/dave/docs

# удаление всех файлов с расширением doc

rm *.docs

Рассматриваемый сценарий выполняет определенные задачи. Каковы же могут быть причины возможных неприятностей? Проблема возникнет, например, в том случае, если нельзя будет создать данный каталог. Как поступить в иной ситуации, если каталог может быть создан, но при копировании файлов появляется сообщение об ошибке? Что произойдет, если применить команду cp к разным файлам из различных каталогов. Продуманные решения нужно принимать до применения команды, а еще лучше, если они реализуются при получении результатов выполнения последней команды. Интерпретатор shell приходит здесь на помощь, поддерживая наборы командных операторов, которые помогают принять верное решение в зависимости от успеха или неудачи при выполнении команды либо при обработке списка. Существует два вида таких командных операторов:

   • операторы цикла;

   • операторы, изменяющие ход выполнения сценария.

 

18.2.1. Операторы, изменяющие ход выполнения сценария

Операторы if, then, else позволяют реализовать условное тестирование. Проверить условия можно самыми различными способами. Например, может производиться оценка размера файла, проверка установленных прав доступа к файлу, сравнение каких‑либо числовых значений или строк. В результате выполнения сравнений возвращается значение "истина" (0) либо "ложь" (1), и затем предпринимаются действия на основании полученного результата. Перед тем как мы приступим к обсуждению условного тестирования, стоит отметить, что некоторые понятия из этой области были рассмотрены ранее.

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

 

18.2.2. Циклические операторы

Цикл, или итерация, — это процесс повторного выполнения наборов команд. В распоряжении пользователя имеется три вида операторов цикла:

for loop Последовательная обработка значений до тех пор, пока не встретится окончание списка
until loop Используется реже всего. Оператор определяет непрерывное выполнение цикла, пока условие не станет истинным. Проверка условия выполняется в конце цикла
while loop Задает выполнение цикла до тех пор, пока не будет встречено заданное условие. Проверка условия выполняется в начале цикла

Каждый цикл, содержащий операторы управления ходом выполнения сценария, может быть вложен. Например, внутри цикла for loop может размещаться другой цикл for loop.

После ознакомления с циклами и процессом управления ходом выполнения сценария рассмотрим некоторые сценарии.

С этого момента все операторы echo в сценариях имеют отношение к Linux или UNIX BSD. То есть при выводе на экран используется метод "echo -e -n", при котором не выполняется создание новой строки в конце вывода. В главе 19 будет показано» как можно воспользоваться универсальной командой echo, которая выполняется для обеих разновидностей системы UNIX (System V и BSD).

 

18.3. Операторы if then else

 

Оператор if позволяет осуществить проверку условий. Проверка выполняется на основе значений "истина" (0) или "ложь" (1), после чего могут вызываться наборы операторов. Конструкция оператора if идеально подходит для проверки ошибок. Этот оператор имеет следующий формат:

if условие1

then

команды1

elif условие2

then

команды2

else

командыЗ

fi

Рассмотрим подробно, какие действия выполняются при вызове оператора if.

if условие1 если условие1 истинно
than тогда
команды1 выполняйте команды1
elif условие2 если условие1 ложно
then тогда
команды2 выполняйте команды2
else если условие1 или условие2 не выполняется
командыЗ тогда выполняйте командыЗ
fi конец

Оператор if обязательно завершается ключевым словом fi. Довольно распространенной ошибкой является пропуск слова f i при закрытии оператора if. Следует отметить, что подобную ошибку могут допускать даже опытные программисты.

Ключевые слова elif и else использовать необязательно. Если оператор не содержит ключевое слово elif, то можно не указывать и else. Оператор if может также включать несколько блоков, начинающихся ключевым словом elif. Основной конструкцией оператора if является конструкция if then fi.

А теперь рассмотрим несколько примеров.

 

18.3.1. Простые операторы if

Базовая структура оператора if выглядит следующим образом:

if условие

then команды

fi

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

if условие; then

команды fi

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

В следующем примере тестовый оператор используется для проверки того, меньше ли число "10" числа "12". Конечно, это условие истинно, и поэтому выполняются операторы, следующие за частью then; в данном случае, на экран просто выводится соответствующее утверждение. Если условие ложно, сценарий завершается, поскольку этот оператор не содержит части else.

$ pg iftest

#!/bin/sh

#iftest

#это строка комментария, все строки комментария начинаются символом # if [ "10" — lt "12" ]

then

# да, 10 меньше 12

echo "Yes, 10 ls less than 12"

fi

 

18.3.2. Проверка значений переменных

Чтобы узнать, задал ли пользователь информацию, можно проверить переменную, которая используется для просмотра вводных данных. Ниже приведены результаты проверки того, присвоены ли какие‑либо данные переменной name после нажатия пользователем клавиши ввода.

$ pg iftest2

#!/bin/sh

# если test2

echo -n "Enter your name :"

read NAME

# правильно ли пользователь ввел данные ????

if [ "$NAME"="" ] ; then

echo "You did not enter any information" fi

$ iftest2

Enter your name :

You did not enter any information

 

18.3.3. Проверка вывода команды grep

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

Чтобы выяснить, была ли выполнена команда grep, можно применить оператор if. В приведенном ниже примере команда grep используется для уточнения, содержится ли в файле data.file слово "Dave". Обратите внимание на то, что при поиске соответствия используется шаблон "Dave\>".

$ pg grepif

#!/bin/sh

# grepif

if grep 'Dave\>' data.file > /dev/null 2>&l

then

echo "Great Dave ls in the file" else

echo "No Dave ls not in the file"

fi

$ grepif

No Dave is not in the file

В примере вывод команды grep направляется в системную корзину. Если поиск значения, совпадающего с шаблоном, завершился удачно, команда grep возвращает

значение 0. В этом случае происходит (естественная интеграция с оператором if; если команда grep успешно завершилась, часть if принимает значение "истина".

 

18.3.4. Проверка вывода команды grep с помощью переменной

Как уже упоминалось, команду grep можно применять в строке. В следующем сценарии пользователь вводит список имен; затем команда grep ищет переменную, которой присвоено имя некого лица (Peter).

$ pg grepstr

#!/bin/sh

# grepstr

echo -n "Enter a list of names:"

read list

if echo $1ist | grep "Peter" > /dev/null 2>&1

then

echo "Peter ls here"

# можно ли выполнить обработку здесь

else

echo "Peter's not in the list. No comment!"

fi

Ниже приводятся результаты вывода, где содержится несколько имен.

$ grepstr

Enter a list of names:John Louise Peter James

Peter is here

 

18.3.5. Проверка результата копирования файла

А теперь осуществим проверку того, успешно ли прошло копирование файла. Если команда cp не скопировала файл myfile в файл myfile.bak, отображается сообщение об ошибке. Обратите внимание, что в сообщении об ошибке фигурирует команда `basename $0` которая выводит на экран название сценария.

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

$ pg ifcp

#!/bin/sh

# ifcp

if cp myfile myfile.bak; then

echo "good copy"

else

echo "`basename $0`: error could not copy the files" >&2 fi

$ ifcp

cp: myfile: No such file or directory

ifcp: error could not copy the files

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

отразиться на выводимых данных; сценарий уже отображает сообщения об ошибках, поэтому известно, что он функционирует неверно. Зачем же нам повторное уведомление? Чтобы избавиться от ошибок, генерируемых системой, и системных данных вывода, достаточно применить перенаправление стандартного потока ошибок и потока вывода. Для этого немного перепишем сценарий: > /dev/null 2>&1. В этом случае получим следующее:

$ pg ifcp

#!/bin/sh

# ifcp

if cp myfile myfile.bak >/dev/null 2>&1; then

echo "good copy"

else

echo "`basename $0`: error could not copy the files" >&2

fi

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

$ ifcp

ifcp: error could not copy the files

 

18.3.6. Проверка текущего каталога

Некоторые сценарии, реализующие административные задачи, можно выполнять из корневого каталога. Если производится глобальное перемещение файлов или же изменяются права доступа к файлу, несложный тест позволяет уточнить, вовлекается ли в этот процесс корневой каталог. В следующем сценарии для хранения текущего каталога переменная DIRECTORY использует подстановку команд. Затем значение этой переменой сравнивается со строкой, содержащей значение "/" (которое и соответствует корневому каталогу). Если значение переменной directory не идентично этой строке, пользователь завершает работу со сценарием. В этом случае код завершения будет 1, что свидетельствует о наличии ошибки.

$ pg ifpwd

#!/bin/sh

# ifpwd DIRECTORY=`pwd`

# захват текущего каталога

if [ "$DIRECTORY" != "/" ]; then

#это корневой каталог ?

#нет, перенаправление вывода в стандартный поток ошибок, который

#отображается на экране по умолчанию.

echo "You need to be in the root directory not $DIRECTORY to run this script" >&2

# выход из сценария со значением 1, ошибка

exit 1

fi

 

18.3.7. Проверка прав доступа к файлу

Вы можете также осуществлять контроль прав доступа к файлу. Ниже приводится несложная проверка на предмет того, можно ли вести записи в файле test.txt, который переприсвоен переменной logfile.

$ pg ifwr

#!/bin/sh

# ifwr

LOGFILE=test.txt echo $LOGFILE

if [ ! —w "$LOGFILE" ]; then

echo " You cannot write to $LOGFILE " >&2

fi

 

18.3.8. Проверка параметров, передаваемых сценарию

Оператор if может применяться при определении числа параметров, которые передаются сценарию. Чтобы проверить, соответствует ли количество необходимых параметров количеству вызываемых параметров, используется специальная переменная $#, содержащая число вызываемых параметров.

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

$ pg ifparam

#!/bin/sh

# ifparam

if [ $# -lt 3 ] ; then

#вызывается меньше, чем 3 параметра, на экран выводится сообщение, затем

#прерывается выполнение сценария

echo "Usage: `basename $0` arg1 arg2 arg3" >&2

exit 1

fi

# хорошо, получено 3 параметра, отображаются на экране

echo "arg1: $1"

echo "arg2: $2"

echo "arg3: $3"

Если передается только два параметра, на экран выводится соответствующее сообщение, и сценарий прекращает выполняться:

$ ifparam cup medal

Usage:ifparam argl arg2 arg3

При передаче трех параметров происходит следующее:

$ ifparam cup medal trophy

argl: cup arg2: medal arg3: trophy

 

18.3.9. Определение интерактивного режима выполнения сценария

Иногда требуется выяснить, выполняется сценарий в интерактивном режиме (режим терминала( либо не в интерактивном режиме (команды cron или at). Такая информация необходима для того, чтобы сценарий мог определить, где можно получить вводимые данные и куда направлять выводимые данные. Чтобы уточнить режим выполнения сценария, достаточно воспользоваться командой test с опцией -t. Если возвращается значение "истина", сценарий выполняется в интерактивном режиме.

$ pg ifinteractive

#! /bin/sh

# ifinteractive

if [ -t ]; then

echo "We are interactive with a terminal"

else

echo "We must be running from some background process probably cron or at " fi

 

18.3.10. Простые операторы if else

Следующая форма оператора if применяется чаще всего:

if условие

then команды1

else

команды2

fi

Если условие не удовлетворяет тестированию, часть else оператора if позволяет перейти к соответствующей операции.

 

18.3.11. Проверка установок переменных

Ниже проверяется установка переменной среды editor. Если переменной EDITOR не присвоено значение, пользователь информируется о том, что переменная editor не установлена. Если переменная editor установлена, тип редактора отображается на экране:

$ рg ifeditor

#!/bin/sh

# ifeditor

if [ -z $EDITOR ]; then

#переменная не установлена

echo "Your EDITOR environment is not set"

else

#посмотрим, что же это

echo "Using $EDITOR as the default editor"

 

18.3.12. Проверка пользователя, выполняющего сценарий

В следующем примере для проверки условия используется переменная среды. Здесь проверяется, присвоено ли переменной LOGNAME значение "root". Обычно этот тип оператора добавляется в начале сценариев в качестве дополнительной меры безопасности. Несомненно, переменная LOGNAME может проверяться для каждого действительного пользователя.

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

Если строка "root" равна значению переменной LOGNAME, выполняется оператор, который находится после else.

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

$ pg ifroot

#!/bin/sh

# ifroot

if [ "$LOGNAME" != "root" ]

# если пользователь не является пользователем root

echo "You need to be root to run this script" >&2

exit 1

else

# да, это пользователь root

echo "Yes indeed you are $LOGNAME proceed"

fi

# выполнение операторов в обычном режиме

 

18.3.13. Передача параметров сценария системной команде

Позиционные параметры можно передать сценарию, а затем проверить значение переменной. Если при этом пользователь указывает после названия сценария наименование каталога, сценарий заново присваивает специальному параметру $1 более содержательное название, в данном случае directory. С помощью команды ls -A проверяется, не является ли каталог пустым. Если каталог пуст, эта команда не возвращает данные. Затем отображается соответствующее сообщение.

$ pg ifdirec

#!/bin/sh

#ifdirec

#присваивание $1 переменной DIRECTORY DIRECTORY=$1

if [ "`ls -A $DIRECTORY/`"="" ] ; then

# если строка пуста, каталог пуст

echo "$DIRECTORY is indeed empty" else

# в противном случае, нет

echo "$DIRECTORY” is not empty"

fi

Вместо указанного метода можно воспользоваться другой возможностью. Получим аналогичный результат.

$ pg ifdirec2

#!/bin/sh

# ifdirec2

DIRECTORY=$1

if [ -z "`ls -A $DIRECTORY`" ] then

echo "$DIRECTORY is indeed empty" else

echo "$DIRECTORY is not empty"

fi

 

18.3.14. Применение команды null

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

К сожалению, нельзя оставлять незаполненными части оператора if — здесь должен находиться какой‑либо оператор. Чтобы разрешить это затруднение, интерпретатор shell поддерживает команду null ':'. Команда null всегда возвращает значение "истина", что в данном случае нас удовлетворяет. Возвращаясь к предыдущему примеру, заметим, что если каталог пуст, команды можно размещать только в части then.

$ pg ifdirectory

#!/bin/sh

# ifdirectory

DIRECTORY=$1

if [ "`ls -A $DIRECTORY`"="" ]

then

echo "$DIRECTORY is indeed empty"

else : # не выполняет ничего

fi

 

18.3.15. Проверка на предмет создания каталога

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

if [ "$DIRECTORY" = "" ]

Вместо предложенного варианта можно воспользоваться общим случаем проверки параметров:

if [ $# -lt 1 ]

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

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

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

$ pg ifmkdir

#!/bin/sh

#ifmkdir

#параметр передается как $1, но заново присваивается переменной DIRECTORY DIRECTORY=$1

#является ли строка пустой ??

if [ "$DIRECTORY"="" ]

then

echo "Usage: `basename $0` directory to create" >&2

exit 1

fi

if [ -d $DIRECTORY ] then :

# ничего не выполняет

else

echo "The directory does exist"

echo -n "Create it now? [y..n] :"

read ANS

if [ "$ANS"="y" ] || [ "$ANS"="Y" ]

then

echo "creating now"

# создайте каталог и перешлите все данные вывода в /dev/null mkdir $DIRECTORY >/dev/null 2>&1 if [ $? != 0 ]; then

echo "Errors creating the directory $DIRECTORY" >&2

exit 1

fi

else :

# ничего не выполняет

fi

При выполнении указанного сценария получим следующее:

$ ifmkdir dt

The directory does exist Create it now? [y..n]: у

creating now

 

18.3.16. Другие возможности копирования

С помощью команды cp сценарию передается два параметра (они должны содержать имена файлов). Затем системная команда cp копирует значение параметра $1 в параметр $2, а поток вывода перенаправляется в /dev/null. Если команда выполнилась успешно, никаких действий не предпринимается, т. е. применяется команда null.

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

$ pg ifcp2

#!/bin/sh

#ifcp2

if cp $1 $2 > /dev/null 2>&1

# успешно, ничего делать не надо

then :

else

# плохо, покажем пользователю, какие файлы здесь были.

echo "`'basename $0`: ERROR failed to copy $1 to $2"

exit 1

fi

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

$ ifср2 myfile.lex myfile.lex.bak

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

$ ifcp2 myfile.lexx myfile.lex.bak

ifcp2: ERROR failed to copy myfile.lexx myfile.lex.bak

В следующем примере для сортировки файла под именем accounts.qtr применяется команда sort.

Результаты вывода направляются в системную корзину. Но кому интересно видеть на экране 300 отсортированных строк? Если сортировка прошла успешно, не нужно предпринимать никаких действий; если при выполнении команды имелись сбои, следует сообщить об этом пользователю.

$ pg ifsort

#!/bin/sh

# ifsort

if sort accounts.qtr > /dev/null

# отсортировано. Прекрасно

then :

else

# лучше сообщим об этом пользователю

echo "`basename $0`: Oops..errors could not sort accounts.qtr"

 

18.3.17. Применение нескольких операторов if

Операторы if можно вкладывать; при этом нужно следить, чтобы каждому ключевому слову if соответствовало слово fi.

 

18.3.18. Проверка и установка переменных среды

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

$ pg ifseted

#!/bin/sh

#ifseted

#установлена ли переменная EDITOR ?

if [ -z $EDITOR ] ; then

echo "Your EDITOR environment ls not set"

echo "2 will assume you want to use vi..OK"

echo -n "Do you wish to change it now? [y..n] :"

read ANS

# проверка верхнего или нижнего регистра для 'у'

if [ "$ANS"="у" ] || [ "$ANS"="Y" ]; then

echo "enter your editor type :"

read EDITOR

if [ -z $EDITOR ] || [ "$EDITOR"="" ]; then

#если переменная EDITOR не установлена, ей не присвоено значение,

#тогда присвоим ей значение vi

echo "No, editor entered, using vi as default"

EDITOR=vi; export EDITOR

fi

# берется значение и присваивается переменной EDITOR

EDITOR=$EDITOR

export EDITOR

echo "setting $EDITOR"

fi

else

# пользователь

echo "Using vi as the default editor"

EDITOR=vi; export EDITOR

fi

Рассмотрим, как работает приведенный сценарий. Сначала проверим, установлена ли эта переменная. Если это так, появляется сообщение, что редактор vi применяется как редактор, заданный по умолчанию. Затем vi устанавливается в качестве редактора, и сценарий завершается.

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

На данном этапе пользователю предлагается ввести тип редактора. Затем выполняется проверка, не установлен ли данный редактор, а также уточняется, не нажимал ли пользователь во время проверки $editor ="" клавишу [Return]. Действительно эта проверка реализована лучше, чем -z $editor, но оба этих метода приводятся лишь в качестве иллюстрации. Если результаты проверки будут отрицательны, на экран выводится сообщение, что применяется редактор vi, причем значение vi присваивается переменной editor.

Если пользователь вводит имя для переменной editor, происходит присвоение и экспорт этого имени.

 

18.3.19. Проверка кода завершения последней команды

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

$ pg ifmkd±r2

#!/bin/sh

#ifmkdir2

DIR_NAME=testdirec

#где мы находимся?

THERE=`pwd`

# перенаправление потока вывода в системную корзину

mkdir $DIR_NAME > /dev/null 2>&1

# каталог ли это ?

if [ -d $DIR_NAME ] ; then

# можно ли применить к каталогу команду cd

cd $DIR_NAME

if [ S? = 0 ]; then

# да, можно

HERE=`pwd`

cp $THERE/*.txt $HERE

else

echo "Cannot cd to $DIR_NAME" >&2

exit 1

fi

else

echo "cannot create directory $DIR_NAME" >&2

exit 1

fi

 

18.3.20. Добавление и проверка целых значений

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

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

Если пользователь вводит у или у, поступает приглашение на ввод нового значения, которое добавляется к счетчику. Если пользователь нажимает клавишу [Return], сценарий с помощью команды echo сообщает, что счетчик сохраняет старое значение. Когда пользователь вводит значение, проверка чисел позволяет уточнить, является ли это значение числом. Если это так, значение добавляется к значению counter и затем отображается на экране.

$ pg ifcounter

#!/bin/sh

# ifcounter

COUNTER=100

echo "Do you wish to change the counter value currently set at $COUNTER ? [y...n] :"

read ANS

if [ "$ANS"="y" ] || [ "$ANS"="Y" ]; then

# да, пользователь желает изменить значение

echo "Enter a sensible value "

read VALUE

#простой тест для уточнения, является ли значение численным,

#добавим к VALUE любое число, проверим код возврата

expr $VALUE + 10 > /dev/null 2>&1

STATUS=$?

# проверим код возврата для expr

if [ "$VALUE"="" ] || [ "$STATUS" != "0" ]; then

# направим ошибки в стандартный поток ошибок

echo " You either entered nothing or a non‑numeric " >&2

echo " Sorry now exiting..counter stays at. $COUNTER" >&2 exit 1 fi

# если мы здесь, значит, это — число, добавим его к COUNTER

COUNTER=`expr $COUNTER + $VALUE`

echo " Counter now set to $COUNTER" else

#если мы здесь, значит, пользователь вместо того, чтобы ввести число,

#нажал клавишу ввода

#или ответим n для изменения значения приглашения

echo " Counter stays at $COUNTER"

fi

Рассмотрим результаты выполнения приведенного сценария.

$ ifcount

Do you wish to change the counter value currently set at 100? [y..n]:n Counter stays at 100

$ ifcount

Do you wish to change the counter value currently set at 100? [y..n]:y

Enter a sensible value: fdg

You either entered nothing or a non‑numeric

Sorry now exiting..counter stays at 100

$ ifcount

Do you wish to change the counter value currently set at 100? [y..n]:y Enter a sensible value: 250

Counter now set to 350

 

18.3.21. Простой сценарий, обеспечивающий безопасность при регистрации

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

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

такого совпадения не наблюдается, пользователь завершает работу.

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

Если вводится корректный ID пользователя и пароль (паролем является mayday), переменные invalid_user и invalid_passwd для недействительного пользователя или пароля имеют значение "по". Затем производится тестирование, и если какая‑либо из переменных принимает значение yes, сценарий для этого пользователя завершается по умолчанию.

К работе допускаются пользователи с действительными ID и паролями. В регистрационном сценарии удобно применять описанное структурное дополнение. В данном примере действительными ID пользователя служат dave или pauline.

$ pg ifpass

#!/bin/sh

#ifpass

#установим значения переменных в "ложь" INVALID_USER=yes

INVALID_PASSWD=yes

# сохранение текущих установок команды stty

SAVEDSTTY=`stty -g`

echo "You are logging into a sensitive area"

echo -n "Enter your ID name :"

read NAME

# скройте символы, введенные в терминале

stty -echo

echo "Enter your password :"

read PASSWORD

# попробуем снова

stty $SAVEDSTTY

if [ "$NAME"="dave" ] || [ "$NAME"="pauline" ]; then

# если действительно, установите переменную

INVALID_USER=no

fi

if [ "$PASSWORD"="mayday" ]; then

# если пароль действителен, установите переменную

INVALID_PASSWD=no

fi

if [ "$INVALIDUSER"="yes" -o "$INVALID_PASSWD"="yes" ]; then

echo " `basename $0 :` Sorry wrong password or userid"

exit 1

fi

# если вы здесь, ваш ID и пароль в порядке.

echo "correct user id and password given"

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

$ ifpass

You are logging into a sensitive area

Enter your ID name : dave

Enter your password :

ifpass :Sorry wrong password or userid

Введем верное имя регистрационное имя и пароль.

$ ifpass

You are logging into a sensitive area

Enter your ID name : dave

Enter your password :

correct user id and password given

 

18.3.22. Применение elif

Часть elif оператора if then else применяется для проверки при наличии более чем двух условий,

 

18.3.23. Несколько проверок, реализуемых с помощью elif

В следующем несложном примере протестируем введенные в сценарий имена пользователей.

Сначала в сценарии проверяется, действительно ли пользователь ввел имя; если имя не введено, то проверка не выполняется. Если имя введено, с помощью части elif проверяется, совпадает ли имя с root, louise или dave. В случае несовпадения имени ни с одним из перечисленных имен на экран выводится сообщение, что пользователь не является пользователем root, louise или dave.

$ pg ifelif

#!/bin/sh

# ifelif

echo -n "enter your login name :"

read NAME

# имя не введено, рассмотрение прекращается

if [ -z $NAME ] || [ "$NAME"="" ]; then

echo "You did not enter a name"

elif

# является ли именем root

[ "$NAME"="root" ]; then echo "Hello root"

elif

# именем является louise

[ $NAME="louise" ]; then echo "Hello louise"

elif

# именем является dave

[ "$NAME"="dave" ]; then echo "Hello dave"

else

# нет, это какое‑то другое имя

echo "You are not root or louise or dave but hi $NAME"

fi

При выполнении приведенного сценария с использованием различных регистрационных имен получим следующее:

$ ifelif

enter your login name : dave Hello dave

$ ifelif

enter your login name : You did not enter a name

$ ifelif2

enter your login name : peter

You are not root or louise or dave but hi peter

18.3.24. Проверка нескольких вариантов размещения файла

Предположим, что к файлу проверки регистрации требуется применить команду cat. Файл в зависимости от того, кто из пользователей выполнял инсталляцию. находится либо в каталоге /usr/opts/audit/logs, либо в каталоге /usr/Iocal/audit/logs. Перед применением к файлу команды cat следует убедиться в том, что его можно просматривать; именно это и будет уточняться при проверке. Если файл нельзя найти или же его нельзя просматривать, на экран выводится сообщение об ошибке. Ниже приводится соответствующий сценарий:

$ pg ifcataudit

#!/bin/sh

#ifcataudit

#размещение файла регистрации

LOCAT_1=/usr/opts/audit/logs/audit.log

LOCAT_2=/usr/local/audit/audit, logs

if [ -r $LOCAT_1]; then

#если файл находится в этом каталоге и может просматриваться,

#применим к нему команду cat echo "Using L0CAT_1"

cat $LOCAT_1

elif

# иначе, файл должен находиться в этом каталоге и можно его просматривать

[ -r §L0CAT_2 ]

then

echo "Using LOCAT_2"

cat $LOCAT_2

else

# нет ни в одном каталоге…

echo `basename $0`: Sorry the audit file ls not readable or cannot be located." >S2

exit 1

fi

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

$ ifcataudit

ifcataudit: Sorry the audit file ls not readable or cannot be located.

 

18.4. Оператор case

 

Оператор case является многовариантным оператором. С его помощью можно искать значения, используя заданный шаблон. Если совпадение с шаблоном установлено, можно выполнять команды, основываясь исключительно на этом соответствии. Ниже приводится формат оператора case:

case значение in шаблон1)

команды1

;;

шаблон2)

команды2

;;

esac

Рассмотрим, как функционирует оператор case. После значения должен находиться предлог "in", а каждый шаблон должен завершаться правой скобкой. В качестве значения может применяться переменная или константа. Когда устанавливается соответствие с шаблоном, для этого шаблона выполняются все команды вплоть до символов ;;.

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

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

* Произвольные символы

? Произвольный отдельный символ

[. . ] Произвольный символ из класса или диапазона

А теперь рассмотрим несколько примеров.

 

18.4.1. Простой оператор case

Следующий сценарий отображает приглашение для ввода чисел от 1 до 5. Число передается оператору case, переменной ans присваивается значение ans оператора case, и значение ANS сравнивается с каждым шаблоном.

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

Далее выполняются операции, находящиеся после оператора case.

Если соответствие не найдено, с помощью шаблона * выполняется прием всей информации. Затем отображается сообщение об ошибке.

$ pg caseselect

#!/bin/sh

# caseselect

echo -n "enter a number from 1 to 5 :"

read ANS

case $ANS in

1) echo "you select 1"

;;

2) echo "you select 2"

;;

3) echo "you select 3"

;;

4) echo "you select 4"

;;

5) echo "you select 5"

;;

*) echo "`basename $0`: This is not between 1 and 5" >&2

;;

exit 1

esac

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

$ caseselect

enter a number from 1 to 5 : 4 you select 4

С помощью шаблона * выполним прием информации, с которой не установлено соответствия:

$ caseselect

enter a number from 1 to 5 :pen

caseselect: This ls not between 1 and 5

 

18.4.2. Применение символа | при поиске по шаблону

При использовании оператора case в качестве команды or можно указывать символ. Например, vt100|vt102) соответствует шаблону vt100 или vt102.

В следующем примере у пользователя запрашивают тип терминала. Если пользователь вводит vt100 или vt102, выполняется сравнение с шаблоном "vtl00| vtl02)". В данном случае переменной term присваивается значение vt100. Если пользователь

указывает тип терминала, который не соответствует шаблону, с помощью шаблона * выполняется прием этой информации и значение типа терминала все равно устанавливается как vt100. Наконец, за пределами действия оператора case производится экспорт переменной term. Независимо от тех сведений, которые вводит пользователь, переменная term представляет действительный тип терминала, поскольку используется поиск по шаблону*.

$ pg caseterm

#!/bin/sh

# caseterm

echo " choices are.. vt100, vtl02, vt220"

echo -n "enter your terminal type :"

read TERMINAL

case $TERMINAL in

vtl00|vt102) TERM=vt100

;;

vt220) TERM=vt220

;;

*) echo "`basename $0`: Unknown response" >&2

;;

echo "setting it to vt100 anyway, so there"

TERM=vt100

esac

export TERM

echo "Your terminal ls set to $TERM"

Если при выполнении сценария указывается некорректный тип терминала, получим следующее:

$ caseterm

choices are.. vt100, vtl02, vt220 enter your terminal type :vt900

caseterm: Unknown response setting it to vt100 anyway, so there

Your terminal ls set to vt100

Если вводится существующий тип терминала, получим:

$ case2

choices are.. vt100, vtl02, vt220 enter your terminal type :vt220

Your terminal ls set to vt220

В любом случае пользователь устанавливает тип терминала.

 

18.4.3. Приглашение для ввода y или n

Оператор case удобно применять при отображении запроса на продолжение обработки. Ниже приводится сценарий, в котором пользователю предлагается указать в качестве ответа либо 'у', что означает продолжение обработки, либо 'n' — выход из сценария. Если пользователь введет у, Y, yes или Yes, обработка будет продолжена в соответствии с частью сценария после оператора case. Если пользователь введет n, N, no или какой‑либо другой ответ, работа со сценарием для него завершается.

$ pg caseans

#!/bin/sh

# caseans

echo -n "Do you wish to proceed [y..n] :"

read ANS

case $ANS in

y|Y|yes|Yes) echo "yes is selected"

;;

n|N) echo "no is selected"

exit 0 # нет ошибки, поэтому для выхода укажите 0

;;

*) echo "`basename S0`" : Unknown response" >&2

exit 1

;;

esac

# если мы оказались здесь, были выбраны значения y|Y|yes|Yes.

Если при выполнении указанного сценария выбрать некорректный вариант ответа, отображаются следующие данные:

$ caseans

Dо you wish to proceed [y..n] :df

caseans : Unknown response

В случае корректного ответа:.

$ caseans

Dо you wish to proceed [y..n] :y

yes is selected

 

18.4.4. Оператор case и передача командных параметров

Можно также использовать оператор case при передаче параметров в сценарии.

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

Затем оператор case выполняет прием следующих параметров: passwd, start, stop или help. В дальнейшем код реализуется для каждого совпадения с этими шаблонами. При передаче неизвестного значения на экран выводится стандартное сообщение об ошибке.

$ pg caseparam

#!/bin/sh

# caseparam

if [ $# != 1 ]; then

echo "Usage:`basename $0` [start | stop | help] >&2

exit 1

fi;

# присвойте переменной ОРТ параметр

OPT=$1

case $OPT in

start) echo "starting… `basename $0`"

# здесь коды для начала процесса

;;

stop) echo "stopping..`basenarae $0`"

# здесь колы для прекращения процесса

;;

help)

# здесь находится код для отображения справочной страницы

;;

*) echo "Usage: `basename $0` [start | stop | help]"

;;

esac

Если сценарию передается неверный параметр, получим следующее:

$ caseparam what

Usage:caseparam [start|stop | help]

В случае передачи действительного параметра:

$ caseparam stop

stopping..caseparam

 

18.4.5. Прием потока ввода без применения шаблонных команд

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

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

В следующем сценарии указано, что если пользователь вводит номер отдела, который не совпадает со значением 234, 453, 655 или 454, то пользователь выйдет из сценария. Если номер отдела указан правильно, аналогичный подход применяется при определении типа отчета. По окончании выполнения оператора case остаются действительный номер отдела и правильный тип отчета. Ниже приводится соответствующий сценарий.

$ pg casevalid

#!/bin/sh

# casevalid

TYPE=""

echo -n "enter the account dept No: :"

read ACC

case $ACC in

234);;

453);;

655);;

454) ;;

*) echo "`basename $0`: Unknown dept No:" >&2

echo "try..234,453,655,454" exit, 1

esac

# если оказались здесь, получен верный номер отдела

echo " 1. post"

echo " 2. prior"

echo -n "enter the type of report: "

read ACC_TYPE

case $ACC_TYPE in

l)TYPE=post;;

2}TYPE=prior;;

*) echo "`basename $0`: Unknown account type." >&2

exit 1

;;

esac

# если оказались здесь, значит все указано правильно!

echo "now running report for dept $ACC for the type $TYPE"

# выполняем отчет о команде.

Если вводимые данные достоверны, получим:

$ casevalid

enter the account dept No: :234

   1. . post

   2. . prior

enter the type of report:2

now running report for dept 234 for the type prior

Если номер отдела введен неверно, получим:

$ casevalid

enter the account dept No: :432

casevalid: Unknown dept No: try..234,453,655,454

При вводе неправильного типа отчета, получим:

$ casevalid

enter the account dept No: :655

   1. . post

   2. . prior

enter the type of report:4

casevalid: Unknown account type.

 

18.4.6. Значения переменных, заданные по умолчанию

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

В следующем сценарии для выполнения отчета пользователю предлагают ввести название дня недели. Если пользователь нажимает клавишу [Return], используется день недели, заданный по умолчанию, а именно "Saturday". Это название и присваивается переменной when.

Если пользователь вводит название другого дня, с помощью оператора case выполняется проверка, совпадает ли введенное название с названием одного из дней недели, предназначенных для выполнения сценария, а именно "Saturday", "Sunday"

и "Monday". Обратите внимание, что всевозможные аббревиатуры названий дней недели составлены так, чтобы выполнялся перехват "всех возможных" комбинаций этих названий.

Ниже приводится соответствующий сценарий.

$ pg caserep

#!/bin/sh

# caserep

echo " Weekly Report"

echo -n "What day do you want to run report [Saturday] :"

#если нажать клавишу ввода, принимается заданное по умолчанию название Saturday

read WHEN

echo "validating..

${WHEN:="Saturday"}"

case $WHEN in Monday|MONDAY|mon);;

Sunday|SUNDAY|sun);;

Saturday|SATURDAY}sat);;

*) echo " Are you nuts!, this report can only be run on " >&2

echo " on a Saturday, Sunday or Monday" >&2

exit 1

esac

echo "Report to run on $WHEN"

# здесь команда для выполнения действительного отчета

При корректных начальных данных получим:

$ caserep

Weekly Report What day do you want to run report [Saturday] : validating..Saturday Report to run on Saturday

Если начальные данные были неправильны, получим:

$ caserep

Weekly Report What day do you want to run report [Saturday] :Tuesday validating..Tuesday

Are you nuts! this report can only be run on a Saturday, Sunday or Monday

Можно заключить, что оператор case функционирует так же, как и несколько операторов if then else. Такой вывод вполне правомерен.

 

18.5. Цикл for

 

Общий формат цикла:

for имя_перемениой in list

do

команда1 команда…

done

Цикл for однократно обрабатывает всю информацию для каждого значения, включенного в список list. Чтобы получить доступ к каждому значению в списке, достаточно задать параметр имя_переменной. Командой служит любая действительная команда или оператор интерпретатора shell. В качестве параметра имя_переменной можно указать любое слово.

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

Опция in list может содержать подстановки, строки и имена файлов. Рассмотрим несколько примеров.

 

18.5.1. Простой цикл for

Этот цикл просто выводит на экран список, который состоит из " 1 2 3 4 5". Чтобы получить доступ к каждой переменной, в качестве параметра имя_переменной указывается "loop".

$ pg for_i

#!/bin/sh

# for_i

for loop in 1 2 3 4 5

do

echo $LOOP done

Приведенный выше сценарий выводит следующие данные:

$ for_i

1

2

3

4

5

 

18.5.2. Вывод на экран строки списка

Ниже приводится цикл for, список которого содержит строку значений "orange red blue grey". Для каждой переменной указана команда echo, в качестве параметра имя_переменной указывается loop. Команда echo с помощью части $LOOP выводит на экран каждое значение списка до тех пор, пока список не окажется пустым.

$ pg forlist

#!/bin/sh

# forlist

for LOOP in orange red blue grey

do

echo $LOOP

done

$ forlist

orange

red

blue

grey

Также с помощью цикла имя_переменной можно комбинировать строки (в данном случае речь идет о цикле loop).

echo "this ls the fruit $LOOP"

Результат:

This ls the fruit orange red blue grey

 

18.5.3. Использование команды ls совместно с циклом for

Этот цикл оценивает команду ls интерпретатора shell и отображает сведения о файлах текущего каталога.

$ pg forls

#!/bin/sh

# forls

for loop in `ls`

do

echo $LOOP

done

$ forls

array

arrows

center

center1

center2

centerb

 

18.5.4. Применение параметров вместе с циклом for

Если в цикле for опустить часть in list, позиционные параметры командной строки становятся аргументами. Действительно, этот подход аналогичен следующему:

for params in "$@"

или

for params in "$*"

Ниже приводится пример, который показывает, как можно избежать применения конструкции in list. Цикл for обращается к специальному параметру $@ или $* для получения аргументов из командной строки.

$ pg forparam2

#/bin/sh

# forparam2

for params in $*

do

echo "You supplied $params as a command line option"

done

echo $params done

$ forparam2 myfile1 myfile2 myfile3

You supplied myfile1 as a command line option

You supplied myfile2 as a command line option

You supplied myfile3 as a command line option

Следующий сценарий содержит часть in "$@" и образует тот же самый поток вывода, что и предыдущий сценарий.

$ pg forparam3

#!/bin/sh

# forparam3

for params in $@

do

echo "You supplied $params as a command line option"

done

echo $params done

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

$ pg forfind

#!/bin/sh

# forfind

for LOOP in $@

do

find / -name $LOOP -print

done

Значения передаются с помощью параметра командной строки и образуют часть -name команды find .

$ forfind passwd LPSO.AKSOP

/etc/passwd

/etс/pam.d/passwd

/etc/uucp/passwd

/usr/bin/passwd

/usr/local/accounts/LPSO.AKSOP

 

18.5.5. Посылка сигналов серверам с помощью цикла for

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

$ pg forping

#!/bin/sh

# forping

HOSTS=”1itserv dnssevr acctsmain ladpd ladware"

for loop in $HOSTS

do

ping -c 2 $LOOP

done

 

18.5.6. Создание резервных копий файлов с помощью цикла for

Цикл for можно использовать для создания резервных копий файлов. При этом переменная просто добавляется к целевому аргументу команды ср. Ниже применяется переменная под названием ВАК. Эта переменная добавляется к каждому имени целевого файла при использовании цикла с помощью команды ср. Список включает shell–команду ls.

$ pg forcp

#!/bin/sh

# forcp

BAK=".bak"

for loop in `ls`

do

echo "copying $LOOP to $LOOP$BAK"

cp $LOOP $LOOP$BAK done

$ forcp

copying array to array.bak

copying arrows to arrows.bak

copying center to center.bak

copying center1 to center1.bak

 

18.5.7. Массовое преобразование

Чтобы найти все файлы, которые начинаются символами "LPSO", и преобразовать их содержимое в символы верхнего регистра, используются команды ls и cat. Команда ls отображает список файлов, а команда cat применяется для передачи списка команде tr. Передаваемые файлы получают расширение UC. Обратите внимание, что при использовании в цикле for команды ls применяются обратные кавычки.

$ pg forUC

#!/bin/sh

# forUC

for files in Us LPSO*'

do

cat $files | tr "[a‑z]" "[A‑Z]" > $files.UC

done

 

18.5.8. Удаления, выполняемые с помощью редактора sed

В следующем примере для удаления всех пустых строк применяется потоковый редактор sed. Выходной поток данных направляется в новые файлы с расширением .HOLD. Затем команда mv возвращает файлам их исходные имена.

$ pg forsed

#!/bin/sh

# forsed

for files in `ls LPSO*` do

sed -e "/^$/d" $files > $files. HOLD

mv $flies. HOLD $files

done

 

18.5.9. Подсчет с помощью циклов

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

$ pg forcount

#!/bin/sh # forcount counter=0

for files in *

do

# increment

counter=`expr $COUNTER + 1`

done

echo "There are $COUNTER files in `pwd` we need to process"

$ forcount

There are 45 files in /apps/local we need to process

Аналогичный результат можно получить с помощью команды wc.

$ ls |wc -l

45

 

18.5.10. Циклы for для обработки документов

С циклом for можно комбинировать любые команды. В приведенном примере переменная содержит имена всех зарегистрированных пользователей. Для реализации этой конструкции обращаются к команде who и утилите awk. Затем цикл for обрабатывает имена этих пользователей и каждому высылает электронное сообщение. При отправке сообщения используется конструкция "документ здесь".

$ pg formallit

#!/bin/sh

# formallit

WHOS_ON=`who -u | awk '{print $1}'`

for user in $WHOS_ON

do

mail $user << MAYDAY

Dear Colleagues,

It's my birthday today, see you down the

club at 17:30 for a drink.

See ya.

$LOGNAME

MAYDAY

done

Ниже приводится электронное сообщение для данного сценария.

$ pg mbox

Dear Colleagues,

It's my birthday today, see you down the

club at 17:30 for a drink.

See ya. dave

 

18.5.11. Вложенные циклы for

Чтобы вложить, циклы, достаточно цикл for поместить в другой цикл:

for имя_переменной in list

do

for имя_переменной2 in list2

do

команда1

done

done

В следующем сценарии представлен вложенный цикл for. Имеются два списка, APPS и SCRIPTS, первый из которых содержит путь к приложениям на сервере, а второй включает административные сценарии, которые выполняются для каждого приложения. Для каждого приложения из списка apps имеется соответствующее название сценария в списке SCRIPTS. Обычно сценарии выполняются в фоновом режиме (с помощью указания префикса &). Данный сценарий посредством команды tee размещает также запись в журнальном файле, поэтому наряду с файлом на экране отображается поток выходных данных. Обратите внимание на выходные данные, чтобы понять, как цикл for использует список SCRIPTS при обработке элементов списка apps.

$ pg audit_run

#!/bin/sh

# audit_run

APPS="/apps/accts /apps/claims /apps/stock /apps/serv"

SCRIPTS="audit.check report.run cleanup"

LOGFILE=audit.log

MY_DATE=`date +%H:%M" on "%d/%m%Y`

# внешний цикл

for loop in $APPS

do

# внутренний цикл

for 1оор2 in $SCRIPTS

do

echo "system $LOOP now running $LOOP2 at $MY_DATE" | tee -a $LOGFILE

$LOOP $LOOP2 &

done

done

$ audit_run

system /apps/accts now running audit.check at 20:33 on 23/051999

system /apps/accts now running report.run at 20:33 on 23/051999

system /apps/accts now running cleanup at 20:33 on 23/051999

system /apps/claims now running audit.check at 20:33 on 23/051999

system /apps/claims now running report.run at 20:33 on 23/051999

system /apps/claims now running cleanup at 20:34 on 23/051999

system /apps/stock now running audit.check at 20:34 on 23/051999

system /apps/stock now running report.run at 20:34 on 23/051999

system /apps/stock now running cleanup at 20:34 on 23/051999

system /apps/serv now running audit.check at 20:34 on 23/051999

system /apps/serv now running report.run at 20:34 on 23/051999

system /apps/serv now running cleanup at 20:34 on 23/051999

 

18.6. Цикл until

 

Цикл until позволяет выполнять ряд команд, пока условие остается истинным. Практически цикл until противоположен по смыслу циклу while. Цикл while является более предпочтительным, но в определенных случаях цикл until лучше справляется с работой. Формат цикла until:

until условие

команда1

done

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

 

18.6.1. Простой цикл until

Этот сценарий непрерывно ищет пользователя "root" и выполняет команду who. Результат выполнения команды grep содержится в переменной IS_ROOT.

Если пользователь "root" обнаружен, цикл завершает обработку. Затем пользователю simon направляется электронное сообщение, в котором отмечается, что пользователь "root" зарегистрирован. Обратите внимание на применение команды sleep, которая довольно часто используется в циклах until. Эта команда дает возможность приостановить выполнение цикла на несколько секунд, что позволяет указать дополнительные команды.

$ pg until_who

#!/bin/sh

# until_who

ls_ROOT=`who | grep root`

until [ "$IS_ROOT" ]

do

sleep 5

done

echo "Watch it. roots in " | mail simon

 

18.6.2. Контроль наличия файла

В этом примере в цикле until выполняется команда "sleep 1" до тех пор, пока не будет удален файл под именем /tmp/monitor. LCK. После удаления файла сценарий продолжает выполняться в обычном режиме.

$ pg until_lck

#!/bin/sh

# until_lck

LOCK_FILE=/trap/process. LCK

until [ ! — f $1OCK_FILE ]

do

sleep 1 done echo "file deleted "

# обычная обработка, файл имеется

В приведенном примере показан один из методов, обеспечивающий совместную работу сценариев при выполнении.

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

Когда выполняется сценарий process_main, создается файл lck (файл блокировки). Приведенный выше сценарий получает информацию, собранную сценарием process_main. Однако он не обрабатывает файл, если сценарий process_main продолжает обновление файла отчета.

Чтобы устранить эти затруднения, сценарий process_main создает файл lck при запуске и удаляет его при завершении работы.

Рассматриваемый сценарий ожидает удаления файла LCK. После того как файл lck удален, сценарий может обрабатывать содержимое файла отчета.

 

18.6.3. Мониторинг дисковой памяти

Цикл until также полезно применять для контроля состояния системы. Предположим, что необходимо уточнить пространство, занимаемое файлами на диске, и направить пользователю "root" электронное сообщение по достижении определенного уровня.

В следующем сценарии просматривается система файлов /logs. С помощью утилиты awk и команды grep информация постоянно извлекается из переменной $LOOK_out. Значения этой переменной характеризуют объем, занимаемый системой файлов /logs.

Если занятый объем превышает 90%, опция command запускает триггер, пользователю "root" направляется сообщение и сценарий завершает работу. Сценарий следует завершить, поскольку в противном случае, если условие истинно (например, занятый объем по–прежнему превышает 90%(, электронное сообщение пользователю "root" будет высылаться непрерывно.

$ pg until_mon

#!/bin/sh

#until_ mon

# получите столбец со значениями процентов и удалите заголовок строки из df

LOOK_OUT=`df | grep /logs | awk '{print $5}' | sed 's/%//g'`

echo $LOOK_OUT

until [ "$LOOK_OUT" -gt "90" ]

do

echo "Filesystem..logs ls nearly full" | mail root

done

exit 0

 

18.7. Цикл while

 

Цикл while выполняет ряд команд до тех пор, пока истинно условие. Этот цикл используется также для просмотра данных из файла ввода. Формат цикла while:

while команда

do

команды1

команды2

done

Между конструкциями while и do находится несколько команд, хотя в общем случае применяется только одна команда. Обычно команда выполняет проверку условия.

Команды, размещенные между ключевыми словами do и done, выполняются только в том случае, если код завершения command равен нулю; если код завершения принимает какое‑либо другое значение, цикл заканчивается.

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

 

18.7.1. Простой цикл while

Ниже приводится основная форма цикла while. Условие тестирования состоит в том, что если "COUNTER is less than 5", условие останется истинным. Переменная counter имеет начальное значение нуль, и ее значение увеличивается на постоянную величину при выполнении цикла.

$ pg whilecount

#!/bin/sh # whilecount COUNTER=0

#счетчик равен 5?

while [ $COUNTER -lt 5 ] do

#прибавление к счетчику единицы

COUNTER=`expr $COUNTER + 1`

echo $COUNTER

done

Указанный сценарий выводит на экран числа от 1 до 5, затем завершает работу.

$ whilecount

1 2 3

4

5

 

18.7.2. Применение цикла while при вводе с клавиатуры

Цикл while может применяться для ввода информации с клавиатуры. В следующем примере введенная информация присваивается переменной film. Если нажать клавиши [Ctrl+D], цикл завершает выполнение.

$ pg whileread

#!/bin/sh

# whileread

echo " type to terminate"

echo -n "enter your most liked film :"

while read FILM

do

echo "Yeah, great film the $FILM"

done

Когда сценарий выполняется, вводимыми данными могут быть:

$ whileread

enter your most liked film: Sound of Music

Yeah, great film the Sound of Music

 

18.7.3. Применения цикла while для считывания данных из файлов

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

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

$ pg names.txt

Louise Conrad:Accounts:ACC8987

Peter James:Payroll:PR489

Fred Terms:Customer:CUS012

James Lenod:Accounts:ACC887

Frank Pavely:Payroll:PR489

Для хранения строк данных можно использовать переменные. Условие истинно до тех пор, пока не считываются новые данные. Для просмотра содержимого файла цикл while использует перенаправление потока данных ввода. Обратите внимание, что отдельной переменной $LINE присваивается целая строка.

$ pg whileread

#!/bin/sh

# whileread

while read LINE

do

echo $LINE

done < names.txt

$ whileread

Louise Conrad:Accounts:ACC8987

Peter James:Payroll:PR489

Fred Terras:Customer:CUS012

James Lenod:Accounts:ACC887

Frank Pavelу:Payroll:PR4 8 9

 

18.7.4. Считывание данных из файлов с помощью IFS

Чтобы при выводе данных устранить разделитель полей в виде двоеточия, примените переменную ifs, предварительно сохранив ее установки. После того как сценарий завершит работу с этими установками, восстановите установки переменной ifs. С помощью переменной ifs можно изменить разделитель полей на двоеточие вместо пробела или символа табуляции, которые заданы по умолчанию. Как известно, отдельной переменной можно присвоить значения трех полей: NAME, dept и id.

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

$ pg whilereadifs

#!/bin/sh

#whilereadifs

#сохраните установку IFS

SAVEDIFS=$IFS

#присвоим переменной IFS новый разделитель

IFS=:

while read NAME DEPT ID

do

echo -e "$NAME\t $DEPT\t $ID" done < names.txt

# восстановим установки переменнойIFS

IFS=$SAVEDIFS

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

$ whilereadifs

Louise Conrad Accounts ACC8987

Peter James Payroll PR489

Fred Terms Customer CUS012

James Lenod Accounts ACC887

Frank Pavely Payroll PR489

 

18.7.5. Обработка файла с помощью проверок условий

Большинство циклов while включает некоторый оператор проверки, который уточняет последовательность действий.

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

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

сократить количество типов. Все редакторские правки находятся в начале, а не рассеяны по всему сценарию.

$ pg whileread_file

#!/bin/sh

#whileread_file

#инициализация переменных

SAVEDIFS=$IFS

IFS=:

HOLD_FILE=hold_file

NAME_MATCH="James Lenod"

INPUT_FILE=names.txt

# создавайте каждый раз новый HOLD_FILE, в случае, когда сценарий

непрерывно выполняется

>$HOLD FILE

while read NAME DEPT ID

do

#выводит на экран всю информацию в holdfile с помощью перенаправления

echo $NAME $DEPT $ID >>$HOLD_FILE

#имеется ли соответствие ???

if [ "$NAME"="$NAME_MATCH" ]; then

# да, тогда удобно завершить работу

echo "all entries up to and including $NAME_MATCH are in $HOLD_FILE"

exit 0

fi

done < $INPUT_FILE

# восстановление IFS

IFS=$SAVEDIFS

Выполним следующий шаг и уточним количество служащих в каждом из отделов. Сохраним прежний формат просмотра, в котором каждому полю присваивается название переменной. Затем для добавления каждого совпадения с помощью оператора case просто применим команду expr. Если обнаруживается неизвестный отдел, его название выводится на экран в виде стандартного потока ошибок; поэтому, если отдел не существует, нет необходимости прерывать выполнение сценария.

$ pg whileread_cond

#!/bin/sh

# whileread_cond

# инициализация переменных ACC_LOOP=0; CUS_LOOP=0; PAY_LOOP=0;

SAVEDIFS=$IFS

IFS=:

while read NAME DEPT ID

do

# счетчик увеличивается на единицу для каждого совпадающего названия отдела.

case $DEPT in

Accounts)

ACC_LOOP=`expr $ACC_LOOP + 1`

ACC="Accounts"

;;

Customer)

CUS_LOOP=`expr SCUS_LOOP + 1`

CUS="Customer"

;;

Payroll)

PAY_LOOP=`expr $PAY_LOOP + 1`

PAY="Pay roll"

;;

*) echo "`basename $0`: Unknown department $DEPT" >&2

;;

esac

done < names.txt

IFS=$SAVEDIFS

echo "there are $ACC_ LOOP employees assigned to $ACC dept"

echo "there are $CUS_LOOP employees assigned to $CUS dept"

echo "there are $PAY_LOOP employees assigned to $PAY dept"

При выполнении сценария получим следующий вывод:

$ whileread_cond

there are 2 employees assigned to Accounts dept

there are 1 employees assigned to Customer dept

there are 2 employees assigned to Payroll dept

 

18.7.6. Выполнение суммирования

Довольно часто приходится сталкиваться с задачей считывания информации из файла и выполнения суммирования по определенным столбцам, содержащим числа. Предположим, в файле total.txt находятся данные о продажах отделами stat и gift..

$ pg total.txt

STAT 3444
GIFT 233
GIFT 252
GIFT 932
STAT 212
STAT 923
GIFT 129

Задача состоит в подсчете общей суммы всех записей отдела gift. Чтобы сохранить общие значения сумм, применим оператор expr. Как показано в следующем операторе expr, переменным loop и total первоначально вне цикла присваивается значение ноль. Когда сценарий выполняет цикл, значение переменной items добавляется к значению переменной total. В первую итерацию цикла входит только первый пункт, но в дальнейшем к накапливающимся значениям переменной total добавляются значения переменной items.

Следующий оператор expr увеличивает значение счетчика.

LOOP=0

TOTAL=0

while…

TOTAL=`expr $TOTAL + $ITEMS`

ITEMS=`expr $ITEMS + 1`

done

Очень распространенной является такая ошибка: при работе с оператором expr забывают сначала инициализировать переменную.

LOOP=0

TOTAL=0

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

TOTAL=`expr ${TOTAL:=0} + ${ITEMS}`

В вышеприведенном примере переменной total присваивается значение нуль, если эта переменная не имеет значения. Чаще распространен первый вариант инициализации переменных с помощью оператора expr. Не забывайте выводить на экран конечное общее значение, полученное в результате выполнения цикла.

Рассмотрим следующий сценарий.

$ pg total

#!/bin/sh

#общая сумма

#инициализация переменных

LOOP=0

TOTAL=0

COUNT=0

echo "items Dept"

echo " "

while read DEPT ITEMS do

# сохраните результаты подсчета при просмотре общих записей

COUNT=`expr $COUNT + 1`

if [ "$DEPT"="GIFT" ]; then

# сохраните выполнение суммирования TOTAL=`expr $TOTAL + $ITEMS` ITEMS=`expr $ITEMS + 1`

echo -e "$ITEMS\t$DEPT"

fi

#echo $DEPT $ITEMS done < total.txt

echo $TOTAL

echo "There were $COUNT entries altogether in the file"

При выполнении сценария получим:

$ total

Items Dept
234 GIFT
253 GIFT
933 GIFT
130 GIFT
====== ======
1546

There were 7 entries altogether in the file

 

18.7.7. Одновременный просмотр двух записей

В некоторых случаях возникает необходимость в одновременной обработке двух записей, например, если нужно сравнить различные поля двух записей. Чтобы просматривать одновременно по две записи, достаточно после первой конструкции "while read" поместить оператор "do" с другим оператором "read". Применяя эту методику, следует помнить, что количество записей для просмотра составляет четное число. Не забывайте проверять число просматриваемых записей!

Ниже приводится файл, содержащий шесть записей: record1, record2 и т. д.

$ pg record.txt

record 1

record 2

record 3

record 4

record 5

record 6

В этом примере одновременно просматривается по две записи. Никакой проверки записей в примере не производится.

Ниже приводится соответствующий сценарий.

$ pg readpair

#!/bin/sh

#readpair

#первая запись

while read rec1 do

#вторая запись

read rec2

#выполняется дальнейшая обработка/тестирование для тестирования или

#сравнения обеих записей

echo "This ls record one of a pair :$rec1"

echo "This ls record two of a pair :$rec2"

done < record.txt

С помощью команды wc сначала проверим, что было выполнено четное число записей.

$ cat record.txt | wc -l

б

Имеется шесть записей, поэтому приступим к их обработке.

$ readpair

This ls record one of a pair :record 1

This ls record two of a pair :record 2

This ls record one of a pair :record 3

This ls record two of a pair :record 4

This ls record one of a pair :record 5

This ls record two of a pair :record 6

 

18.7.8. Игнорирование символа #

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

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

Однако с помощью оператора case можно игнорировать некоторые строки, начинающиеся с определенных символов. Поскольку символ # является специальным, для его отключения используется символ "\"; затем после символа хэша указывается звездочка, что позволит после хэша размещать любые символы.

Ниже приводится типичный файл конфигурации.

$ pg config

#ЭТО КОНФИГУРАЦИОННЫЙ ФАЙЛ ПОДСИСТЕМЫ АУДИТА

#НЕ РЕДАКТИРУЙТЕ ЕГО! ОН РАБОТАЕТ

#

# задание административного доступа

AUDITSCM=full

#местонахождение подсистем

AUDITSUB=/usr/opt/audit/sub

#серийный номер хэша для продукта

HASHSER=12S90AB3

#КОНЕЦ ФАЙЛА КОНФИГУРАЦИИ!!!

#

Ниже приводится сценарий, где игнорируются символы хэша:

$ pg ignore_hash

#!/bin/sh

# игнорируйте_хэш

INPUT_FILE=config

if [ -s $INPUT_FILE ]; then

while read LINE

do

case $LINE in

\#*) ;; # игнорировать все символы хэша

*) echo $LINE

;;

esac

done < $INPUT_FILE

else

echo "`basename $0` : Sorry $INPUT_FILE does not exist or ls empty"

exit 1

fi

При выполнении получим:

$ ignore_hash

AUDITSCM=full

AUDITSUB=/usr/opt/audit/sub

HASHSER=12890AB3

 

18.7.9. Работа с форматированными отчетами

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

$ pg order

RE‑ORDER REPORT
ITEM ORDERLEVEL LEVEL
Pens 14 12
Pencils 15 15
Pads 7 3
Disks 3 2
Sharpeners 5 1

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

Ранее уже рассматривалось, каким образом можно игнорировать строки, начинающиеся определенными символами. Сначала производится чтение из файла. При этом игнорируются все строки, которые начинаются символами # и строками вида "ITEM". После прочтения осуществляется перенаправление данных во временный файл. Затем команда sed применяется для удаления пустых строк. На самом деле все, что делается в этом случае, напоминает фильтрацию текстового файла. Описанные действия реализованы с помощью следующего кода.

$ pg whileorder

#!/bin/sh

# whileorder

INPUT_FILE=order

HOLD=order.tmp

if [ -s $INPUT_FILE ]; then

# пустой файл вывода, добавление не производится!

>$HOLD

while read LINE

do

case $LINE in

\#*|ITEM*) ;; # игнорирование строк, содержащих символы # или ITEM

*)

# перенаправление вывода во временный файл

echo $LINE >$HOLD

;;

esac

done <$INPUT_FILE

# применение команды sed для удаления пустых строк

sed -e '/^$/d' order.tmp > order.$$

mv order.$$ order.tmp

else

echo "`basename $0` : Sorry $INPUT_FILE does not exist or ls empty"

fi

В результате выполнения сценария получаем следующее:

$ pg order.tmp

Pens 14 12

Pencils 15 15

Pads 7 3

Disks 3 2

Sharpeners 5 1

Теперь остается только выполнить считывание временного файла в другом цикле while, а затем реализовать некоторые сравнения с помощью команды expr. Вот соответствующий сценарий:

$ pg whileorder2

#!/bin/sh

#whileorder2

#инициализация переменных

HOLD=order.tmp

RE_ORDER=0

ORDERS=0

STATIONERY_TOT=0

if [ -s $HOLD ]; then

echo "=========== STOCK RE_ORDER REPORT ================"

while read ITEM REORD LEVEL

do

#находимся ли мы ниже уровня переупорядочивания для данного пункта??

if [ "$LEVEL" — lt "$REORD" ]; then

да, выполняется заказ другого количества товаров

NEW_ORDER=`expr $REORD + $REORD`

# подсчет итогов по заказам

ORDERS=`expr $ORDERS + 1`

# подсчет итогов по уровням запасов

STATIONERY_TOT=`expr $STATIQNERY_TOT + $LEVEL`

echo "$ITEM need reordering to the amount $NEW_ORDER"

done <$HOLD

echo "$ORDERS new items need to be ordered"

echo "Our reorder total ls $STATIONERY_TOT"

else

echo "`basename $0` : Sorry $HOLD does not exist or ls empty"

fi

Результат выполнения сценария при обработке файла заказов.

$ whileorder

========= STOCK REORDER REPORT ===============

Pens need reordering to the amount 28

Pads need reordering to the amount 14

Disks need reordering to the amount 6

Sharpeners need reordering to the amount 10

4 new items need to be ordered

Our reorder total is 18

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

 

18.7.10. Цикл while и дескрипторы файлов

При изучении дескрипторов файлов в главе 5 уже упоминалось о том, что для считывания данных в файл применяется цикл while. С помощью дескрипторов файлов 3 и 4 следующий сценарий создает резервную копию файла myfile.txt под именем myfile.bak. Обратите внимание, что в начале сценария осуществляется проверка, которая позволяет убедиться в наличии файла. Если файл отсутствует или не содержит данные, выполнение сценария немедленно прекращается. Также обратите внимание на то, что в цикле while имеется команда null (:). Из‑за наличия этой команды цикл может выполняться бесконечно, поскольку null всегда возвращает значение "истина". При осуществлении попытки считывания по достижении конца файла отображается сообщение об ошибке. При этом выполнение сценария прекращается.

$ pg copyfile

#!/bin/sh

# copyfile

FILENAME=myfile.txt

FILENAME_BAK=myfile.bak

if [ -s $FILENAME ]; then

#открыть FILENAME для записи

#открыть FILENAME для считывания'

exec 4>$FILENAME_BAK

exec 3<$FILENAME

#бесконечный цикл до тех пор, пока имеются данные или пока не возникает

#ошибка, связанная с достижением конца файла while :

do

read LINE <&3

if [ "$?" -ne 0 ]; then

# ошибки при закрытии

exec 3<&-

exec 4<&-

exit fi

# запись в файл FILENAME_BAK

echo $LINE>&4

done else

echo "`basename $0` : Sorry, $FILENAME is not present or is empty" >&2

fi

 

18.8. Управление ходом выполнения циклов с помощью команд break и continue

 

Иногда в процессе работы возникает необходимость в прерывании или пропуске отдельных итераций цикла. При этом применяются определенные критерии. Для обеспечения подобных возможностей интерпретатор shell предлагает две команды:

   • break;

   • continue.

 

18.8.1. Команда break

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

 

18.8.2. Прекращение выполнения оператора case

Рассмотрим следующий пример. В сценарии выполняется бесконечный цикл до тех пор, пока пользователь не введет число, большее 5. Для прерывания цикла и возврата в командную строку интерпретатора используется команда break.

$ pg breakout

#!/bin/sh

   • breakout

   • while : бесконечный цикл while :

do

echo -n "Enter any number [1..5] :"

read ANS

case $ANS in

1|2|3|4|5) echo "great you entered a number between 1 and 5"

;;

*) echo "Wrong number..bye"

break

;;

esac

done

 

18.8.3. Команда continue

Команда continue по своему действию напоминает команду break, за исключением одной существенной детали: она не прерывает цикл, а лишь приводит к пропуску текущей итерации цикла.

 

18.8.4. Пропуск строк в файлах

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

$ pg names2.txt

LISTING OF PERSONNEL FILE

TAKEN AS AT 06/1999

Louise Conrad:Accounts:ACC8987

Peter James:Payroll:PR489

Fred Terms:Customer:CUS012

Janes Lenod:Accounts:ACC887

Frank Pavely:Payroll:PR4S9

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

Также не следует принимать во внимание сведения о сотруднике с именем Peter

james Этот человек уволился из компании, но запись о нем осталась в файле.

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

Вот сценарий, выполняющий описанные задачи

$ pg whilecontinue

#!/bin/sh

# whilecontinue

SAVFDIFS=$IFS

IFS=:

INPUT_FlLE=names2.txt

NAME_HOLD="Peter James"

LINE_NO=0

if [ -s $INPUT_FILE ]; then

while read NAME DEPT ID

do

LINE_NO=`expr $LINE_NO + 1`

if [ "$LINE_NO" — le 2 ]; then

# допуск, если номер строки меньше 2 continue

if [ "$NAME"="$NAME_HOLD" ], then

# пропуск, если переменной NAME_HOLD присвоено имя Peter James

continue

else

echo " Now processing $NAME $DEPT $ID"

# обработка файла

fi

done < $INPUT_FILE

IFS=$SAVEDIFS

else

echo "`basename $0` : sorry file not found or there is no data in the file" >&2

exit 1

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

$ whilecontinue

Luise Conrad Accounts ACC8987

Fred Terns Customer CJS012

James Lenod Accounts ACC887

Frank Pavely Payroll PR389

 

18.9. Меню

При создании меню представляется весьма удобным использование команды null совместно с циклом while. Объединение этих конструкций приводит к бесконечному циклу, что и требуется в меню Цикл должен выполняться до тех пор, пока пользователь не осуществит выход или не выберет нужную опцию

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

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

Ниже приводится примерный вид меню.

Сначала воспользуемся подстановкой команд для назначения даты, имени хоста и пользователя. Для указания даты используется формат dd/mm/yyyy. Этот формат задается с помощью следующего параметра:

$ date +%d/%B/%Y

32/05/1999

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

Присвоим переменным осмысленные имена:

MYDATE=`date +%d/%m/%Y`

THIS_H0ST=`hostname -s`

USER=`whoami`

В цикле while команда null помещена непосредственно после слова "while". Вот формат бесконечного цикла:

while : do

команды..

done

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

команда << СЛОВО

любые вводимые данные

СЛОВО

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

Для обработки результатов выбора пользователя используется конструкция case. Варианты выбора меню будут следующими:

1: List files in current directory

2: Use the vi editor

3: See who ls on the system

H: Help screen

Q: Exit Menu

Оператор case должен обрабатывать все шаблоны, переводя все строчные символы шаблона в прописные. При этом устраняется риск случайного нажатия пользователем клавиши [Caps Lock], в результате чего могут искажаться вводимые данные. В случае, когда сценарий меню выполняет бесконечный цикл, пользователь нуждается в реализации элегантного выхода. В связи с этим, если пользователь выбирает клавишу [Q] или [q], сценарий должен осуществить выход с нулевым значением.

Если пользователь выполняет некорректный ввод, раздается звуковой сигнал и отображается предостерегающее сообщение. Хотя в начале этой главы упоминалось о том, что будут применяться операторы echo Linux BSD, для генерации звукового сигнала будет применена команда из версии System V:

echo '"\007 the bell rang"

Конструкции echo и read применяются для задержки отображения экрана до тех пор, пока пользователь не нажмет клавишу [Enter]. При этом могут просматриваться любые сообщения или вывод команд.

В конце концов, потребуется очистить экран. Эта задача выполняется с помощью команды tput (о применении этой команды мы поговорим позже) в том случае, когда не используется clear. А теперь посмотрите на сам сценарий.

$ pg menu

#!/bin/sh # меню

# установка даты, имени пользователя и хоста

MYDATE=`date +%d/%m/%Y`

THIS_HOST= `hostname -s` USER=`whoami`

# бесконечный цикл!

while :

do

# очистка экрана с помощью команды tput

# здесь начинается конструкция "документ здесь"

cat <

# завершение конструкции "документ здесь"

echo -e -n "\tYour Choice [1,2,3,H, Q] >"

read CHOICE

case $CHOICE in

1) ls

;;

2) vi

;;

3) who

;;

H|h)

# использование конструкции "документ здесь" для экрана помощи

cat <

This "ls the help screen, nothing here yet to help you!

MAYDAY

;;

Q|q) exit 0

*) echo -e "\t\Q07unknown user response"

;;

esac

echo -e -n "\tHit the return key to continue"

read DUMMY

done

 

18.10. Заключение

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

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

 

ГЛАВА 19

 

Функции интерпретатора shell

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

Интерпретатор команд shell позволяет группировать наборы Команд или конструкций, создавая повторно используемые блоки. Подобные блоки называются shell–функциями.

В данной главе будут рассмотрены следующие темы:

   • определение функций;

   • работа с функциями в сценарии;

   • использование функций, определенных в файле функций;

   • примеры функций.

Функция состоит из двух частей:

Метка функции

Тело функции

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

Формат, применяемый для определения функций:

имя_функции ( )

{

Команда1}

или

имя_функции () {

команда 1 \

}

Приемлемы оба способа определения функции. Можно также использовать ключевое слово function перед именем функции имя функции.

function имя_функции ()

{

….

}

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

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

 

19.1. Объявление функций в сценарии

Вот пример простой функции:

hello ()

{

echo "Hello there today's date is `date`"

}

Перед использованием функций их необходимо объявить. Суть объявления заключается в том, что все функции должны быть размещены в начале кода сценария. Невозможно сослаться на функцию до тех пор, пока она не попадет в "поле зрения" интерпретатора команд. Для вызова функции требуется просто ввести ее имя. В предыдущем примере функция называлась "hello"; тело функции включало конструкцию echo, которая, в свою очередь, отображала текущую дату.

 

19.2. Использование функций в сценарии

После создания функции рассмотрим методы ее применения в сценарии.

$ pg fund

#!/bin/sh

# func1

hello ()

{

echo "Hello there today's date ls `date`"

)

echo "now going to the function hello"

hello

echo "back from the function"

При выполнении этого сценария получаются следующие результаты:

$ fund

now going to the function hello

Hello there today's date ls Sun Jun 6 10:46:59 GMT 2000

back from the function

В предыдущем примере функция была объявлена в начале сценария. Для обращения к функции просто вводится ее имя, которое: в данном случае звучит как "hello". После завершения выполнения функции управление возвращается следующей конструкции, которая размещена после вызова функции. В приведенном примере речь идет о конструкции echo "back from the function".

 

19.3. Передача параметров функции

Порядок передачи параметров функции аналогичен передаче параметров обычному сценарию. При этом используются специальные переменные $1, $2, … $9. При получении функцией переданных ей аргументов происходит замена аргументов, изначально переданных сценарию интерпретатора shell. В связи с этим неплохо было бы повторно присвоить значения переменным, получаемым функцией. В любом случае это стоит сделать, поскольку при наличии ошибок в функциях их можно будет легко обнаружить, воспользовавшись именами локальных переменных. Для вызывающих аргументов (переменных), находящихся Внутри функции, имя каждой переменной начинается с символа подчеркивания, например: _FILENAME или _filename.

 

19.4. Возврат значения функции

После естественного завершения выполнения функции либо в том случае, когда она завершается в результате выполнения какого‑либо условия, можно выбрать один из двух возможных вариантов:

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

   2. Воспользоваться ключевым словом return, в результате чего будет осуществлена передача управления конструкции, которая расположена за оператором вызова функции. При этом может также указываться необязательный числовой параметр. Этот параметр принимает значение 0 в случае отсутствия ошибок и значение 1 — при наличии ошибок. Действие этого параметра аналогично действию кода завершения последней команды. При использовании ключевого слова return применяется следующий формат:

return возвращает результат из функции, использует код завершения последней команды для проверки сосстояния
return 0 применяется при отсутствии ошибок
return 1 применяется при наличии ошибок

 

19.5. Проверка значений, возвращаемых функцией

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

check it ls_a directory $FILENAME

# вызов функции и проверка

if [ $?=0 ]

# применение кода завершения последней команды для тестирования

then

echo "All ls OK" else

echo "Something went wrong!"

fi

Лучшим методом является использование оператора if, с помощью которого осуществляется проверка возвращаемого значения (0 или 1). Встраивание вызова функции в структуру оператора if значительно улучшает читабельность программного кода. Например:

if check_it_is_a_directory $FILENAME; then

echo "All is OK"

# действия

else

echo "Something went wrong!"

# действия

fi

Если функцию планируется использовать для отображения результатов некоторой проверки, для фиксации результата применяется подстановка. Формат, используемый для выполнения подстановки при вызове функции, будет следующим:

имя_переменной=`имя_функции`

Выводимый результат функции имя_функции присваивается переменной имя_переменной.

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

 

19.6. Файл функций

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

В начале файла функции должна находиться конструкция #!/bin/sh. Этому файлу можно присвоить любое имя, но все же лучше использовать какое‑либо осмысленное наименование, например functions.main.

Как только файл будет загружен интерпретатором shell, появляется возможность вызова функций из командной строки либо из сценария. Чтобы просмотреть все определенные функции, воспользуйтесь командой set. Поток вывода будет содержать все функции, которые были загружены интерпретатором shell.

Для изменения любой из ранее определенных функций сначала примените команду

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

 

19.7. Создание файла функций

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

Создаваемый файл функций functions.main будет содержать следующий код:

$ pg functions.main

#!/bin/sh

# functions.main

#

findit: интерфейс для базовой команды find

findit () (

#findit

if [ $# -lt 1 ]; then

echo "usage: findit file"

return 1

find / -name $1 -print

Код, приведенный выше, ранее уже упоминался в книге, но теперь он включен в состав функции. Этот код лежит в основе интерфейса для базовой команды find . Если команде не передаются аргументы, то возвращается значение 1 (что свидетельствует о возникновении ошибки). Обратите внимание, что ошибочная конструкция фактически является отображенным именем функции (если же была использована команда $0, интерпретатор команд просто возвращает сообщение sh). Причина отображения подобного сообщения заключается в том, что файл не является файлом сценария. В любом случае это сообщение не несет много информации для пользователя.

 

19.8. Подключение файла функций

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

. /путь/имя_файла

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

$. functions.main

Если в результате выполнения этой команды возвращается сообщение 'file not found' (файл не найден), попытайтесь воспользоваться следующей командой:

$ . ./functions.main

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

 

19.9. Проверка загруженных функций

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

$ set

USER=dave

findit=()

{

if [ $# -lt 1 ]; then

echo "usage findit file";

return 1; fi; find / -name $1 -print

}

 

19.10. Вызов функций интерпретатора shell

 

Для вызова функции просто введите ее имя (в данном случае findit) и укажите аргумент, в роли которого может выступать файл, размещенный в системе.

$ findit groups

/usr/bin/groups

/usr/local/backups/groups.bak

 

19.10.1. Удаление shell–функций

Теперь настало время немного изменить функцию. Сначала функция будет удалена, в результате чего она не будет доступна интерпретатору shell. Для выполнения этой операции применяется команда unset. При вызове данной команды используется следующий формат:

unset имя_функции $ unset findit

Если вы сейчас введете команду set, функция не будет найдена.

 

19.10.2. Редактирование shell–функций

А теперь изменим файл fiinctions.main. Добавим в функцию цикл for, в результате чего сценарий сможет считывать более одного параметра из командной строки. Функция приобретет следующий вид:

$ pg functions.main

#!/bin/sh findit ()

{

# findit

#if [ $# -lt 1 3; then

echo "usage: findit file"

return 1 fi

for loop do

find / -name $LOOP -print done }

Снова загрузим исходный файл:

$ . ./functions.main

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

$ set

findit=()

{

if [ $# -lt 1 ]; then

echo "usage :`basename $0` file";

return 1; fi;

for loop in "$@"; do

find / -name $LOOP -print; done }

Далее вызывается измененная функция findit. При этом поддерживаются два файла с целью осуществления поиска:

$ findit LPSO.doc passwd

/usr/local/accounts/LPSO.doc

/etc/passwd

 

19.10.3. Примеры функций

Теперь, когда вы получили начальные сведения о функциях, рассмотрим их практическое применение.

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

Подтверждение ввода

Рассмотрим небольшой сценарий, который запрашивает имя и фамилию пользователя:

$ pg func2

#!/bin/sh

# func2

echo -n "What ls your first name :"

read F_NAME

echo -n "What ls your surname:"

read S_NAME

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

char_name()

{

#char_name

#вызов: char_name string

#назначение аргумента новой переменной

_LETTERS_ONLY=$1

#использование awk для проверки на наличие символов!

_LETTERS_ONLY=`echo $1 | awk '{if [$0~/[^a‑zA‑Z]/} print "1"}'`

if [ "$_LETTERS_ONLY" != "" ] then

# ошибки

return 1

else

# содержит только символы

return 0

fi }

Сначала переменной $1 будет присвоено более осмысленное имя. Затем применяется утилита awk, осуществляющая проверку, состоит ли переданная строка из одних литер. В результате выполнения возвращается код, включающий 1 (для символов, не являющихся литерами) и 0 — для символов–литер. Этот код присваивается переменной _LETTERS_ONLY.

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

Для проверки вывода функции можно использовать формат, применяемый для оператора if:

if char_name $F_NAME; then

echo "OK"

else

echo "ERRORS"

fi

Если происходит ошибка, можно создать другую функцию, отображающую сообщение об ошибке:

name_error ()

# name_error

# отображение сообщения об ошибке

{

echo " $@ contains errors, it must contain only letters"

}

Функция name_error будет отображать сообщения об ошибках, игнорируя при этом все некорректные записи. Применение специальной переменной $@ позволяет отображать на экране значения всех аргументов. В рассматриваемом случае будет отображено либо значение f_name, либо значение $NAME. А теперь приведем завершенный сценарий, созданный с применением функций:

$ pg func2

#!/bin/sh

char_r.ame {)

# наименование_символа

# вызов: char_name строка

# проверка на предмет того, действительно ли $1 содержит только символы a‑z.,A‑Z

{

# присвоение аргумента новой переменной

_LETTERS_ONLY=$1

_:LETTERS_ONLY=`echo $1|awk '{ if ($0~/[^a‑zA‑Z]/) print "1"}`

if [ "$_LETTERS_ONLY" != "" ]

then

# присутствуют ошибки

return 1

else

# содержит только символы

return 0

fi }

name_error()

# отображение сообщения об ошибке

{

echo " $@ contains errors, it must contain only letters"

}

while : do

echo -n "What ls your first name :"

read F_NAME

if char_name $F_NAME

then

# все OK, завершение выполнения break else

name_error $F_NAME fi done

while : do

echo -n "What ls your surname :"

read S_NAME

if char_narae $S_NAME

then

# все OK, завершение выполнения

break else

name_error $S_NAME

fi

done

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

Ниже приведены результаты выполнения описанного сценария:

$ func2

What ls your first name: Davi2d

Davi2d contains errors, it must contain only letters

What ls your first name: David

What ls your surname: Tansley1

Tansley1 contains errors, it must contain only letters

What ls your surname: Tansley

Проблемы с конструкцией echo

В системах Linux, BSD или System V конструкция echo по–разному интерпретирует служебные символы. Создадим функцию, определяющую систему, в которой используется конструкция echo.

После того как была применена конструкция echo, командная строка может и далее отображаться на экране, ожидая ввода данных со стороны команды read.

Для реализации описанного поведения в системах Linux и BSD совместно с командой echo применяется опция -n. Ниже приводится пример конструкции echo LINUX (BSD), когда командная строка продолжает отображаться на экране до момента завершения сценария:

$ echo -n "Your паше :"

Your name : {{?}}

В System V в этом случае применяется параметр \с:

$ echo "Your name :\c"

Your name : []

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

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

При использовании первого метода проверка управляющего символа происходит внутри конструкции echo. Если после ввода команды echo \007 прозвучал звуковой сигнал, значит, перед нами System V. Если отображается строка "\007", значит, текущей операционной системой является Linux.

Вот первая функция, выполняющая проверку на наличие управляющих символов.

uni_prompt ()

   • uni_prompt

   • универсальная конструкция echo

{

if [ `echo "\007"` ="\007" ] >/dev/null 2>&1

# слышен звуковой сигнал либо отображаются символы?

then

# отображаются символы, это LINUX/BSD

echo -e -n "$@"

else

# это System V

echo "$@\c"

fi }

И снова обратите внимание на применение специальной переменной $@ для отображения строки. Для вызова функции в сценарии можно использовать следующую команду:

uni_prompt "\007There goes the bell. What is your name :"

В результате выполнения этой команды выдается звуковой сигнал и отображается строка "What is your name", которая остается на экране.

Для проверки на наличие новой строки можно отобразить любой символ, используя версию команды echo \c системы System V. Если символ "зависает" в конце строки, мы имеем дело с System V; если же нет, значит у нас система Linux/BSD.

Второй метод заключается в проверке того, будет ли литера Z "зависать" в конце строки. При этом используется опция \с системы System V.

uni_prompt ()

#uni_prompt

#универсальная командная строка

{

if [ `echo "Z\c"`="Z" ] >/dev/null 2>&1

then

# System V

echo "$@\c"

else

# LINUX/BSD

echo -e -n "$@"

fi }

Для вызова данной функции из сценария применяется команда:

uni_prompt "\007 There goes the bell, What ls your name :"

Вызов любой из описанных выше функций возможен с помощью следующего кода:

uni_prompt "\007 There goes the bell, What is your name :"

read NAME

В результате мы получим:

There goes the bell, What ls your name :

Чтение одиночного символа

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

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

dd if=/dev/zero of=myfile count=512 bs=2048

Команда dd также может интерпретировать результаты ввода с клавиатуры и использоваться для чтения символов. В данном случае ожидается появление лишь одного символа. Команда dd должна завершать выполнение после нахождения символа новой строки; этот управляющий символ появляется после нажатия пользователем клавиши [Return]. Команда dd в данном случае также посылает один символ. Перед тем как произойдет одна из описанных ситуаций, необходимо установить терминал в исходный режим. Для этого применяется команда stty. Настройки в приведенном ниже коде сохраняются перед вызовом команды dd и затем восстанавливаются после завершения выполнения команды dd.

read_a_char()

#read_a_char {

#сохранение настроек SAVEDSTTY=`stty -g`

#задание параметра терминала stty cbreak

#чтение и вывод лишь одного символа

dd if=/dev/tty bs=l count=l 2> /dev/null

# восстановление параметра терминала и настроек

stty -cbreak

stty $SAVEDSTTY }

Для вызова функции и возврата введенного символа используется подстановка команд. Вот соответствующий пример.

echo -n "Hit Any Key To Continue"

character=`read_a_char`

echo " In case you are wondering you pressed $character"

Проверка наличия каталога

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

is_it_a_directory()

{

# is_it_a_directorу

# вызов: is_it_a_directory имя_каталога

if [ $# -lt 1 ]; then

echo "is_it_a_directory: I need an argument" return 1 fi

# это каталог ?

_DIRECTORY_NAME=$1

if [ ! -d $_DIRECTORY_NAME ]; then

# нет

return 1

else

# да

return 0

fi

}

Для вызова функции и проверки результата можно воспользоваться кодом:

echo -n "enter destination directory :"

read DIREC

if is_it_a_directory $DIREC;

then :

else

echo "$DIREC does not exist, create it now? [y..n]"

# здесь должны находится команды для создания каталога или для выхода

fi

Запрос на ввод Y или N

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

Create a directory

Do you wish to delete this file

Run the backup now

Confirm to save a record

Этот перечень может быть достаточно длинным.

Следующая функция реализует обобщенный запрос. Пользователь указывает отображаемое сообщение, а также ответ, выбираемый по умолчанию. Ответ, выбираемый по умолчанию, используется в том случае, если пользователь просто нажал клавишу [Return]. Конструкция case применяется для перехвата ответов.

continue_prompt ()

#continue_prompt

#вызов: continue_prompt "отображаемая_строка" ответ_по_умолчанию

{

_STR=$1

_DEFAULT=$2

# проверка на предмет указания правильных параметров

if [ $# -lt 1 ]; then

echo "continue_prompt: I need a string to display"

return 1

fi

# бесконечный цикл

while :

do

echo -n "$_STR [Y..N] [$_DEFAULT]:" read _ANS

#если пользователь нажал [Return], устанавливаются настройки

#по умолчанию и определяется возвращаемое значение,

#ниже находится пробел, а затем символ $

: ${_ANS:=S_DEFAULT}

if [ "$_ANS" = "" ]; then

case $_ANS in

Y) return 0 ;;

N) return 1 ;;

esac

fi

# пользователь что‑то выбрал

case $_ANS in

y|Y|Yes|YES)

return 0

;;

n|N|No|NO)

return 1

;;

*) echo "Answer either Y or N, default is $_DEFAULT"

;;

esac

echo $_ANS

done }

Для вызова функции можно указать отображаемое сообщение в двойных кавычках либо вызвать ее вместе с аргументом $1, либо, в крайнем случае, использовать переменную, содержащую строку. Также может передаваться ответ, заданный по умолчанию, в виде 'Y' или 'N'.

Ниже демонстрируется несколько методов, с помощью которых можно вызвать функцию continue_prompt.

if continue_prompt "Do you want to delete the var filesytem" "N"; then

echo "Are you nuts!!"

else

echo "Phew, what a good answer!"

fi

При использовании этого кода можно указывать следующий ввод:

Do you really want to delete the var filesystem [Y..N] [N] :

Phew, what a good answer!

Do you really want to delete the var filesystem [Y..N] [N] :y

Are you nuts!!

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

Ниже приводится другой способ вызова функции:

#if continue_prompt "Do you really want to print this report" "Y"; then

lpr report

else:

fi

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

if continue_prompt $1 "Y"; then

lpr report

else :

fi

Получение сведений об идентификаторе регистрации

При работе в составе большой системы может возникнуть необходимость связаться с одним из зарегистрированных пользователей. Что делать в том случае, если вы не помните имя пользователя? Часто системные администраторы наблюдают идентификаторы пользователей, блокирующих какие‑либо процессы. Для того чтобы увидеть полное имя пользователя, они должны просмотреть файл passwd с помощью команды grep. И только после этого можно связаться с пользователем, чтобы сделать ему справедливый выговор.

А сейчас мы рассмотрим функцию, которая позволит избежать просмотра файла /etc/passwd с помощью команды grep.

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

Функции передается один либо множество идентификаторов Пользователей, а функция имитирует действие команды grep по отношению к файлу passwd.

Программный код функции:

whois ()

#whois

#вызов: whois идентификатор_пользователя

{

# проверка на наличие корректных параметров

if [ $# -lt 1 ]; then

echo "whois : need user id's please"

return 1

fi

for loop do

_USER_NAME=`grep $LOOP /etc/passwd | awk -F: '(print $4}'`

if [ "$_USER_NAME"="" ]; then

echo "whois: Sorry cannot find $LOOP"

else

echo "$LOOP is $_USER_NAME" fi

done

}

Функция whois может быть вызвана следующим образом:

$ whois davs peters superman

dave ls David Tansley — admin accts

peter ls Peter Stromer -customer services

whois: Sorry cannot find superman

Использование нумерации в текстовом файле

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

number_file ()

#number_file

#вызов: number_file имя_файла {

_FILENAME=$1

# проверка наличия корректных параметров

if [ $# -ne 1 ]; then

echo "number_flie: I need a filename to number"

return 1

fi

loop=1

while read LINE do

echo "$LOOP: $LINE" loop=`expr $LOOP + 1`

done < $_FILENAME

}

Для вызова функции number_file просто укажите имя файла, подлежащего нумерации, в качестве аргумента функции. Вызов функции может также осуществляться из среды интерпретатора shell путем указания имени файла. Например:

$ number_file myfile

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

$.number_file $1

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

$ number_file /home/dave/file_listing

1: total 105

2: -rw‑r—r—- 1 dave admin 0 Jun 6 20:03:DT

3: -rw‑r--r-- 1 dave admin 306 May 23 16:00 LPSO. AKS

4: -rw‑r--r-- 1 dave admin 306 May 23 16:00 LPSO. AKS. UC

5: -rw‑r--r-- 1 dave admin 324 May 23 16:00 LPSO. MBB

6: -rw‑r--r-- 1 dave admin 324 May 23 16:00 LPSO. MBB. UC

7: -rw‑r--r-- 1 dave admin 315 May 23 16:00 LPSO. MKQ

Преобразование символов в прописные

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

Ниже приводится соответствующая функция. Эта функция имитирует действие команды tr:

str_to_upper ()

#str_to_upper

# вызов: str_to_upper $1

{

STR=$1

# проверка на наличие корректных параметров

if [ $# -ne 1 ]; then

echo "number_file: I need a string to convert please"

return 1

fi

echo $@ |tr '[a‑z]' '[A‑Z]'

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

UPPER=`str_to_upper "documents.live"`

echo $UPPER

либо указать аргумент функции вместо строки:

UPPER=`str_to_upper $1`

echo $UPPER

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

is_upper

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

оператора if В сценарии позволит определить, будет ли передаваемая строка включать прописные символы.

Программный код функции:

is_upper ()

#is_upper

#вызов: is_upper $1

{

# проверка на наличие корректных параметров

if [ $# -ne 1 ]; then

echo "is_upper: I need a string to test OK"

return 1

fi

# применение awk для проверки на наличие прописных символов

_IS_UPPER=`echo $1 | awk '{if($0~/[^A‑Z]/) print "1"}`

if [ "$_IS_UPPER" != "" ]

then

# нет, не все символы являются прописными

return 1

else

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

return 0

fi }

При вызове функции is_upper укажите строковый аргумент. На примере показано, как вызывается функция.

echo -n "Enter the filename :"

read FILENAME

if ls_upper $FILENAME; then

echo "Great it's upper case" else

echo "Sorry it's not upper case" fi

Для проверки наличия в строке строчных символов просто замените существующую конструкцию awk а функции is_upper и измените имя функции на is_lower.

_IS_LOWER=`echo $1 |awk '{ if ($0~/[^a‑z] /) print "1"}`

Преобразование символов строки в строчные символы

В предыдущем разделе мы рассмотрели функцию str_to_upper, а теперь речь пойдет о функции str_to_lower. Вот код самой функции:

str_to_lower ()

#str_to_lower

#вызов: str_to_lower $1

{

#проверка на наличие корректных параметров

if [ $# -ne 1 ]; then

echo "str_to_lower: I need a string to convert please"

return 1

fi

echo $@ | tr '[A‑Z]' '[a~z]'

}

Переменная lower хранит возвращенное значение строки, содержащей строчные символы. Обратите внимание на повторное использование специального параметра $@ для передачи всех аргументов. Функция str_to_lower может быть вызвана двумя способами. Во–neрвых, можно указать строку в сценарии:

LOWER=`str_to_lower "documents.live"`

echo $LOWER

Альтернативный вариант — указать аргумент для функции вместо задания строки:

LOWER=`str_to_lower $1`

echo $LOWER

Определение длины строки

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

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

Вот сама функция:

check_length()

#check_length

#вызов: check_length строка максимальная_длина_строки

{

_STR=$1

_МАХ=$2

# проверка на наличие корректных параметров

if [ $# -ne 2 ]; then

echo "check_length: I need a string and max length the string should be" return 1

fi

# проверка длины строки

_LENGTH=`echo $_STR |awk '{print length ($0)}'`

if [ "$_LENGTH" -gt "$_MAX" ]; then

# длина строки слишком велика

return 1

else

# строка имеет обычную длину

return О

fi }

Функция check length может быть вызвана следующим образом:

$ pg test_name

#!/bin/sh

# test name

while : do

echo -n "Enter your FIRST name :"

read NAME

if check_length $NAME 10

then break.

# ничего не происходит, если все условия выполнены else

echo "The name field ls too long 10 characters max"

fi

done

Цикл продолжает выполняться до тех пор, пока данные, вводимые для переменной NAME, меньше, чем значение переменной MAX (эта переменная содержит количество разрешенных символов; в данном случае речь идет о 10 символах). Команда break позволяет завершить выполнение цикла.

При использовании приведенного выше фрагмента кода, вывод будет следующим:

$ val_max

Enter your FIRST name :Pertererrrrrrrrrrrrrrr

The name field ls too long 10 characters max

Enter your FIRST name :Peter

Команда wc также может применяться для определения длины строки, но имейте в виду следующий факт. При использовании команды wc для обработки результатов ввода с клавиатуры могут появиться проблемы. Если после ввода имени несколько раз нажать клавишу пробела, то, как правило, некоторые пробелы будут учитываться в качестве части строки. По этой причине будет определяться некорректная длина строки. Утилита awk "обрезает" конечные пробелы в строке при осуществлении ввода с клавиатуры (эта функция задана по умолчанию).

Ниже приведен пример, иллюстрирующий сказанное:

echo -n "name :"

read NAME

echo $NAME | wc -c

Результат выполнения описанного фрагмента сценария (здесь [] является пробелом):

name : Peter[][]

Функция chop

Функция chop удаляет символы в начале строки. Этой функции передается строка; пользователь указывает, сколько символов необходимо "обрезать", начиная с первого символа. Предположим, что имеется строка mydocument.doc и требуется "обрезать" часть mydocument, в результате чего функция будет возвращать только часть .doc. При этом функции chop могут быть переданы следующие параметры:

MYDOCUMENT.DOC 10

Код функции chop:

chop () # chop

# вызов: chop строка количество_обрезаемых_символов

#

STR=$1

_CHOP=$2

# подстрока awk, начинается с 0, нам потребуется прирастить ее на единицу # для отображения того, что если пользователь задал обрезание 2 символов, 2 символа будут удалены а не 1

CHOP=`expr $_CHOP + 1`

#проверка на корректность параметров

if [ $# -ne 2 ]; then

echo "check_length: I need a string and how many characters to chop"

return 1

fi

# первоначальная длина строки

# мы не можем обрезать больше символов, чем содержится в строке!!

_LENGTH=`echo $_STR | awk '{print length ($0)}'`

if [ "$_LENGTH" — lt "$_CHOP" ]; then

echo "Sorry you have asked to chop more characters than there are in the string"

return 1

fi

echo $_STR | awk '(print substr ($1, '$_CHOP')}'

}

Возвращаемая строка, которая была "обрезана", присваивается переменной chopped. Для вызова функции chop используется следующая последовательность:

CHOPPED=`chop "Honeysuckle" 5`

echo $CHOPPED

suckle

Вызов также можно осуществить другим способом:

echo -n "Enter the Filename :"

read FILENAME

CHOPPED=`chop $FILENAME 1`

# первый символ будет обрезан !

Функция months

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

Например, использование в качестве аргумента значения 3 либо 03 приведет к возврату значения "March". Вот описание самой функции:

months () {

# months

_MONTH=$1

# проверка на наличие корректных параметров

if [ $# -ne 1 ]; then

echo "months: I need a number 1 to 12 "

return 1

fi

case $_MONTH in

1|01|Jan)_FULL="January";;

2|02|Feb)_FULL="February";;

3|03|Mar)_FULL="March";;

4|04|Apr)_FULL="April";;

5|05|May)_FULL="May";;

6|06|Jun)_FULL="June";;

7|07|Jul)_FULL="July";;

8|08|Aug)_FULL="August;;

9|09|Sep|Sept)_FULL="September";;

10|Oct)_FULL="October";;

ll|Nov)_FULL="November;;

l2|Dec)_FULL="December";;

*) echo "months: Unknown month"

return 1

;;

esac

echo $_FULL

}

Для вызова функции months можно применить одни из следующих методов.

months 04

В результате отобразится наименование месяца "April". Можно также вызвать функцию из сценария:

MY_MONTH=`months 06`

echo "Generating the Report for Month End $MY_MOMTH"

В результате отобразится название месяца "June".

 

19.10.4. Подведение итогов

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

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

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

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

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

 

19.11. Вызов функций

 

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

 

19.11.1. Вызов функций, размещенных в сценариях

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

$ pg direc_check

#!/bin/sh

# файл функций

is_it_a_directory()

{

#is_it_a_directory(

#вызов: is_it_a_directory имя_каталога

_DIRECTORY_NAME=$1

if [ $# -lt 1 ]; then

echo "is_it_a_directory: I need a directory name to check"

return 1

fi

# это каталог?

if [ ! —d $_DIRECTORY_NAME ]; then

return 1

else

return 0

fi

}

#

error_msg

{

#error_msg

#сигнал; сообщение; повторный сигнал

echo -e "\007"

echo $@

echo -e "\007"

return 0

}

### END OF FUNCTIONS

echo -n "enter destination directory :"

read DIREC

if is_it_a_directory $DIREC

then :

else

error_msg "$DIREC does not exist…creating it now"

mkdir $DIREC > /dev/null 2>&1

if [ $? != 0 ]

then

error_msg "Could not create directory: check it out!"

exit 1

else :

fi

fi # не каталог

echo "extracting files…"

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

 

19.11.2. Вызов функций из файла функций

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

А теперь воспользуемся снова описанной выше функцией, но в этом случае поместим ее в файле функций. Назовем этот файл functions.sh, где "sh" означает "shell scripts" (сценарии интерпретатора shell).

$ pg functions.sh

#!/bin/sh

#functions.sh

#основные функции

is_it_a_directory () (

#is_it_a_directory

#вызов: is_it_a_directory имя_каталога #

if [ $# -lt 1 ]; then

echo "is_it_a_directory: I need a directory name to check"

return 1

fi

# это каталог ?

DIRECTORY_NAME=$1

if [ ! -d $DIRECTORY_NAME ]; then

return 1 else

return D fi )

error_msg ()

{

echo -e "\007"

echo $@

echo -e "\007"

return 0

}

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

. /<путь к файлу>

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

$ pg dlrec_check

#!/bin/sh

# direc_check

# загрузка файла функций functions.sh # ниже точка, пробел и косая черта. /home/dave/bin/functions.sh

# теперь могут использоваться функции

echo -n "enter destination directory :"

read DIREC

if is_it_a_directory $DIREC

then : .

else

error_msg "$DIREC does not exist… creating it now"

mkdir $DIREC > /dev/null 2>$1

if [ $? ! — 0 ]

then

error_msg "Could not create directory: check it out!"

exit 1

else :

fi

fi # не является каталогом

echo "extracting files…"

При выполнении сценария получается тот же вывод, что и при встраивании функции в сценарий:

S direc_check

enter destination directory :AUDIT

AUDIT does not exist… creating it now

extracting files…

 

19.12. Загрузка файлов, которые состоят не только из функций

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

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

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

$ pg backfunc

#!/bin/sh

   • name: backfunc

   • конфигурационный файл содержит настройки по умолчанию для систем архивации _CODE="comet"

_FULLBACKUP="yes" _LOGF1LE="/logs/backup/" _DEVICE="/dev/rmt/0n" _INFORM="yes" _PRINT_STATS="yes"

Комментарии разъясняют суть программы. Первое поле, _code, содержит кодовое слово. Для просмотра его содержимого и изменения значений пользователь должен ввести код, соответствующий значению code. В данном случае указывается слово "comet".

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

$ pg readfunc

#!/bin/sh

# readfunc

if [ -r backfunc ]; then

# указание файла с параметрами

. /backfunc else

echo "`basename $0` cannot locate backfunc file" fi

echo -n "Enter the code name :"

# соответствует ли указанный код коду из файла backfunc?

if [ "${CODE}" != "${_CODE}" ]; then

echo "Wrong code…exiting..will use defaults" exit 1 fi

echo ." The environment config file reports"

echo "Full Backup Required : $_FULLBACKUP"

echo "The Logfile is : $_LOGFILE"

echo "The Device To Backup To is : $_DEVICE" echo "You Are To Be Informed by Mail : $_INFORM" echo "A Statistic Report To Be Printed: $_PRINT_STATS"

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

$ readfunc

Enter the code name :comet

The environment config file reports

Full Backup Required : yes

The Logfile ls : /logs/backup/

The Device To Backup To ls : /dev/rmt/0n You Are To Be Informed by Mail : yes A Statistic Report To Be Printed: yes

 

19.13. Заключение

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

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

 

ГЛАВА 20

 

Передача параметров сценарию

В предыдущих главах рассматривались способы передачи параметров сценариям с помощью специальных переменных $1...$9. Специальная переменная $# указывает количество передаваемых параметров. Также обсуждалась конструкция usage. Эта конструкция применяется для информирования пользователя о том, как вызвать сценарий или функцию с помощью соответствующих параметров вызова. В этой главе будут рассмотрены следующие темы:

   • применение команды shift;

   • работа с командой getopts;

   • примеры использования команд shift и getopts.

Для проверки степени усвоения материала рассмотрим схему сценария, в котором используются параметры start и stop. При запуске сценария необходимо указать два параметра. Если они не заданы, отображается предупреждающее сообщение. Обратите внимание, что для обработки различных параметров, передаваемых сценарию, применяется конструкция case.

$ pg opt

#!/bin/sh

# opt

usage ()

{

echo "usage:`basename $0` start|stop process name"

}

OPT=$1

PR0CESSID=$1

#if [ $# -ne 2 ]

then

usage

exit 1 fi

case $OPT in

start|Start) echo "Starting..$PROCESSID"

# выполняется некоторая обработка

;;

stop|Stop) echo "Stopping..$PROCESSID" # выполняется некоторая обработка

;;

*) usage

;;

esac

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

$ opt start named

Starting…named

$ opt start

usage:opt start|stop process name

Общий формат произвольной команды UNIX или Linux: команда опции файлы

Часть опции может принимать до 12 различных значений. Как показано в примере со сценарием opt, для работы с командными опциями следует создавать большой объем программного кода. В данном случае мы имеем дело лишь с двумя опциями, start и stop.

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

 

20.1. Команда shift

 

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

$ pg opt2

#!/bin/sh

# opt2

loop=0

while [ $# -ne 0 ] # цикл выполняется до тех пор, пока остаются аргументы

do

echo $1 done

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

$ opt2 файл1 файл2 файлЗ

файл1 файл1 файл1

 

20.1.1. Простой способ использования команды shift

Для обработки каждого передаваемого аргумента достаточно воспользоваться командой shift. Ниже приводится соответствующий сценарий:

$ pg opt2

#!/bin/sh

# oPt2

1оор=0

while [ $# -ne 0 ] # цикл выполняется до тех пор, пока остаются аргументы do

echo $1

shift

done

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

$ opt2 файл1 файл2 файл3

файл1 файл2 файл3

 

20.1.2. Последний параметр командной строки

Несмотря на то что команда eval еще не обсуждалась, можно воспользоваться ею, если требуется уточнить последний параметр командной строки (обычно в качестве этого параметра используется имя файла). Получить последний параметр командной строки вы можете двумя способами. Во–neрвых, с помощью команды eval echo \$$#, а во–вторых, путем применения команды shift `expr $# - 2`.

 

20.1.3. Преобразования файла с помощью команды shift

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

При выполнении сценария используются две опции:

-l для нижнего регистра -u для верхнего регистра

С помощью команды shift можно запустить на выполнение сценарий, работающий с опциями -l и -u. Ниже приводится первый вариант этого сценария.

$ pg tr_case

#!/bin/sh

#tr_case

#преобразование регистра usage ()

{

# сообщение usage

echo "usage:`basename $0` -[l|u] file [files]" >&2

exit 1

}

if [ $# -eq 0 ]; then

# параметры не переданы!

usage

fi

while [ $# -gt 0 ]

do

case $1 in

-u|-U) echo " -uoption specified"

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

# примените команду shift shift

;;

-l|-L) echo " -l option specified"

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

# примените команду shift shift

;;

*) usage

;;

esac

done

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

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

$ tr_case -u -l -к

— и option specified

-l option specified

usage: tr_case —[l|u] file [files]

На следующем этапе обрабатываются файлы, передающиеся после обработки опций с помощью конструкции case. Для реализации этой задачи достаточно выполнить небольшие изменения. В конструкции case шаблон * заменяется шаблоном -*, что позволяет передавать некорректные опции, например -p или -q.

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

Итак, улучшенный вариант конструкции case имеет следующий вид:

case

...

-*) usage

*) if [ -f $1 ]; then

FILES=$FILES" "$1 # присвоить переменной имена файлов

else

echo "`basename $0` cannot find the file $1"

fi

shift # получите следующий параметр !

esac

Следует также задать значения некоторых переменных в зависимости от указанной опции (-1, — u). При этом используются следующие переменные:

TRCASE Указывает тип преобразования регистра (верхний или нижний регистр)
ЕХТ Все преобразованные файлы имеют либо расширение .UC (для верхнего регистра), либо расширение .LC (для нижнего регистра). В исходный файл изменения не вносятся
OPT При передаче опций принимает значение yes, в противном случае — no. Только тогда, когда опции не передаются, можно перехватить их значение и вывести на экран сообщение

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

$ pg tr_case

#!/bin/sh

#tr_case

#преобразование символов файлов либо в верхний, либо в нижний регистр

FILES=""

TRCASE=""

EXT=""

OPT=no

# вызывается при неудачном преобразовании

error_msg ()

{

_FILENAME=$1

echo "`basename $0`: Error the conversion failed on $_FILENAME"

}

if [ $# -eq 0 ] then

echo "For more info try `basename $0` —help"

exit 1

fi

while [ $# -gt 0 ]

do

case $1 in

# установите переменные на базе применяемой опции

-u)

TRCASE=upper

ЕХТ=".UС"

OPT=yes

shift

;;

-l)

TRCASE=lower

EXT=".LC"

OPT=yes

shift

;;

-help) echo "convert a file(s) to uppercase from lowercase" echo "convert a file(s) from lowercase to uppercase" echo "will convert all characters according to the" echo " specified command option," echo " Where option ls"

echo " -l Convert to lowercase"

echo " -uConvert to uppercase"

echo " The original file(s) ls not touched. A new file(s)"

echo "will be created with either a. UC or. LC extension"

echo "usage; $0 -[l|u] file [file..]"

exit 0

;;

-*) echo "usage: `basename $0` -[l|u] file [file..]"

exit 1

;;

*) # сбор файлов для обработки

if [ -f $1]

then

# добавьте имена файлов в список переменных

FILES=$FILES" "$1

else

echo "`basename $0`: Error cannot find the file $1"

fi

shift

;;

esac

done

# опции не заданы… помогите пользователю

if [ "$OРТ"="no" ]

then

echo "`basename $0`: Error you need to specify an option. No action taken"

echo " try `basename $0` --help"

exit 1

fi

#просмотр всех файлов

#используется переменная LOOP, такое красивое слово LOOP

for LOOP in $FILES

do

case $TRCASE in

lower) cat $LOOP | tr "[a‑z]" "[A‑Z]" >$LOOP$EXT

if [ $? != 0 ]

then

error_msg $LOOP

else

echo "Converted file called $LOOP$EXT"

fi

;;

upper} cat $LOOP|tr "[A‑Z]" "[a‑z]" >$LOOP$EXT

if [ $? != 0 ]

then

error_msg $LOOP

else

echo "Converted file called $LOOP$EXT"

fi

;;

esac

done

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

При передаче имени несуществующего файла:

$ tr_case -k cursor

usage: shiftl -[l|u] file [file..}

При передаче некорректных опций:

$ tr_case cursor

tr case:Error you need to specify an option. No action taken try tr_case -help

Если просто указать имя файла в надежде на ответную помощь сценария, результатом будет:

$ tr_case

For more info try tr_case -help

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

$ tr_case -l cursor sd ascii

tr case: Error cannot find the file sd

Converted file called cursor.LC

Converted file called ascii.LC

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

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

command -1 -с 23 -v файл1 файл2

Здесь нельзя применить команду shift; вместо нее следует воспользоваться командой getopts.

 

20.2. Команда getopts

 

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

 

20.2.1. Пример сценария, использующего команду getopts

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

-a Переменной all присваивается значение "истина"
-h Переменной help присваивается значение "истина"
-f Переменной file присваивается значение "истина"
-v Переменной verbose присваивается значение "истина'

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

$ pg getopt1

#/bin/sh #getopt1

# присвоение значений переменным

ALL=false

HELP=false

FILE=false

VERBOSE=false

while getopts ahfgv OPTION do

case $OPTION in

a)ALL=true

echo "ALL is $ALL"

;;

h)HELP=true

echo "HELP is $HELP"

;;

f)FILE=true

echo "FILE is $FILE"

;;

v)VERBOSE=true

echo "VERBOSE is $VERBOSE"

;;

esac

done

Общий формат команды getopts:

getopts строка_параметров переменная

А теперь используем код из нашего примера:

while getopts ahfgv OPTION

Нетрудно заметить, что цикл while применяется для считывания в командной строке. Параметр строка параметров включает пять указанных опций (-a, —h, —f, —g, —v), а также переменную, которая в данном примере именуется OPTION. Заметьте, что не требуется при определении каждой одиночной опции указывать дефис.

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

$ getopt1 -a -h

ALL is true

HELP is true

$ getopt1 -ah

ALL is true

HELP is true

$ getopt1 -a -h -p

ALL is true

HELP is true

./getopt1: illegal option —p

Обратите внимание, что возможно комбинирование различных опций.

 

20.2.2. Принцип работы команды getopts

Команда getopts считывает строку строка_параметров. При этом она выбирает корректные опции, которые могут быть применены в сценарии.

Команда getopts разыскивает все аргументы, начинающиеся дефисом, и определяет значения всех опций. Затем значение опции сравнивается со строкой строка_параметров. Если соответствие установлено, переменной присваивается значение option. В противном случае переменной присваивается значение ?. Этот процесс продолжается до тех пор, пока не будут обработаны все опции.

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

 

20.2.3. Указание значений опций с помощью команды getopts

Иногда для сценариев требуется включение фактического значения одной из опций командной строки. При этом используется команда getopts. Все, что требуется для этого сделать, — вставить двоеточие после буквы опции параметра строка_параметров. Например:

getopts ahfvc: OPTION

Эта команда определяет передачу опций a, h, f, v без указания значений, но опция с должна иметь значение. После указании значения оно будет присвоено переменной OPTARG. Если попытаться передать данную опцию без этого значения, отобразится сообщение об ошибке. Стандартное сообщение об ошибке не является особо информативным, поэтому "подавите" его отображение и выполните следующее:

Укажите двоеточие перед параметром строка_параметров.

while getopts :ahfgvc: OPTION

Используйте оператор usage внутри конструкции case. При этом применяется символ ?, выполняющий функции перехвата ошибок.

case

\?) # оператор usage

echo "`basename $0` -[a h f v] -[с value] file"

esac

Ниже представлен измененный сценарий getopt1:

$ pg getopt1

#!/bin/sh

#getopt1

# установка значений переменных

ALL=false

HELP=false

FILE=false

VERBOSE=false

COPIES=0 # значение опции -c равно нулю

while getopts :ahfgvc: OPTION do

case $OPTION in

a)ALL=true

echo "ALL is $ALL"

;;

h)HELP=true

echo "HELP is $HELP"

;;

f}FILE=true

echo "FILE is $FILE"

;;

v)VERBOSE=true

echo "VERBOSE" is $VERBOSE"

;;

C)COPIES=$OPTARG

echo "COPIES is $COPIES"

;;

\?) # оператор usage

echo "`basename $0` —[ahfv] —[c value] file" >&2

;;

esac done

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

$ getopt1 -ah -с

ALL is true

HELP is true

getopt1 —[ahfv] -[c value] file

Теперь указываются все допустимые опции:

$ getopt1 -ah -с 3

ALL is true HELP is true COPIES is 3

 

20.2.4. Доступ к значениям

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

$ pg backups

#!/bin/sh

# backups

QUITE=n

DEVICE=awa

LOGFILE=/tmp/logbackup

usage ()

(

echo "Usage: `basename $0` -d [device] — l [logfile] — q"

exit 1

}

if [ $# - 0 ]

then

usage fi

while getopts :qd:l: OPTION do

case $OPTION in

q) QUIET=y

LOGFILE="/tmp/backup.log"

;;

d) DEVICE=$OPTARG

;;

l) LOGFILE=$OPTARG

;;

\?) usage

;;

esac done

echo "you chose the following options..I can now process these"

echo "Quite=$QUITE $DEVICE $LOGFILE"

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

$ backups -d /dev/rmt0 -q

you chose the following options..

I can now process these Quite=у /dev/rmt0 /tmp/backup.log

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

Вот и все, что требуется знать о том, каким образом команда getopts обеспечивает передачу параметров командной строки.

Для фактической обработки файлов используется цикл for, подобно тому, как это было в сценарии tr case, где применялась команда shift для работы с опциями.

Применение команды getopts позволяет радикально сократить объем создаваемого кода по сравнению с использованием метода shift-

 

20.2.5. Использование команды getopts для преобразования файлов

А теперь воспользуемся сценарием tr_case, который преобразуем с помощью только что изученной команды getopts. Существует единственное отличие между методами getopts и shift, применяемыми для обработки опций командной строки. Это отличие заключается в том, что в первом случае используется опция verbose.

Переменная verbose имеет значение "по", заданное по умолчанию; но при перехвате значения опции командной строки с помощью конструкции case переменной verbose присваивается значение "yes". Отображение команд на экране осуществляется с помощью простой конструкции if.

if [ "VERBOSE"="on" ]; then

echo "doing upper on $LOOP..newflie called $LOOP$EXT"

fi

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

command > /dev/null 2>&1

По умолчанию переменная verbose не установлена (нет отображения). Активизировать эту переменную можно посредством опции -v. Например, для преобразования серии файлов myfiles в символы нижнего регистра с помощью VERBOSE применяется следующий формат:

tr_case -1 -v myfilel myfile2…

либо

tr_case -v -1 myfilel myfile2…

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

Пример сценария:

$ pg tr_case2

#!/bin/sh

#tr_case2

# преобразование регистра, используется команда getopts

ЕХТ=""

TRCASE=""

FLAG=""

OPT="no"

VERBOSE="off"

while getopts :luv OPTION

do

case $OPTION in

l)

TRCASE="lower"

EXT=".LC"

OPT=yes

;;

u)

TRCASE="upper"

EXT=".UC"

OPT=yes

;;

v)

VERBOSE=on

;;

\?) echo "usage: `basename $0`: -[l|u] —v file[s]"

exit 1

;;

esac

done

#следующий аргумент, пожалуйста

shift `expr $OPTIND -1`

#есть аргументы ???

if [ "$#"="0" ] || [ "$OPT"="no" ] then

echo "usage: `basename $0`: -[l|u] — v file[s]" >&2

exit 1

fi

for LOOP in "$@" do

if [ ! — f $LOOP ] then

echo "`basename $0`: Error cannot find file $LOOP" >&2

exit 1

fi

echo $TRCASE $LOOP

case $TRCASE in

lower)

if [ "VERBOSE"="on" ]; then

echo "doing…lower on $LOOP..newflie called $LOOP$EXT"

fi

cat $LOOP | tr "[a‑z]" "[A‑Z]" >$LOOP$EXT

;;

upper) if [ "VERBOSE"="on" ]; then

echo "doing upper on $LOOP..newflie called $LOOP$EXT"

fi

cat $LOOP | tr "[A‑Z]" "[a‑z]" >$LOOP$EXT

;;

esac

done

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

Опция Значение
-a добавление
-c счетчик, копирование
-d каталог, устройство
-e выполнение
-f имя файла, форсировать
-h справка
-i игнорировать регистр
-1 журнальный файл
-o полный вывод
-q полностью
-p путь
-v многословный

 

20.3. Заключение

Способность корректно обрабатывать опции командной строки придает сценариям профессиональный вид. Для пользователя эти опции выглядят точно так же, как любые другие системные команды. В этой главе были рассмотрены два метода, предназначенные для обработки опций командной строки, — shift и getopts. Размер программного кода, необходимого для внедрения метода getopts, является намного меньшим, чем в случае использования метода shift.

Метод shift также позволяет преодолеть ограничения параметров $1…$9, проявляющиеся при их передаче сценариям. При использовании метода shift сценарий просто выполняет "смещение" среди всех вызываемых аргументов, благодаря чему можно выполнять дальнейшую обработку.

 

ГЛАВА 21

 

Создание экранного вывода

С помощью shell–сценариев можно создавать профессионального вида экраны, позволяющие реализовать интерактивное взаимодействие пользователя с системой. Для этого достаточно располагать цветным монитором и использовать команду tput.

В главе рассматриваются следующие темы:

   • применение команды tput;

   • использование escape–последовательностей и генерирование управляющих кодов;

   • pабота с цветом.

Известно, что существует три различных варианта команды tput. Наилучшим из них является команда tput GNU. Если в системе отсутствует эта версия команды, загрузите и установите ее. Команда tput использует файл /etc/terminfo или файл /etc/termcap. В shell–сценариях можно применять большинство команд, поддерживаемых терминалом.

Команда tput не распознает настройки цвета. Для работы с цветом используются управляющие символы.

 

21.1. Применение команды tput

 

Чтобы применить команду tput, следует инициализировать установки терминала, обращаясь к команде tput с помощью сценариев или командной строки.

$ tput init

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

 

21.1.1. Строчный поток вывода данных

Ниже приводятся общие строки из потока вывода:

Название Значение
bel Звуковой сигнал
blink Режим мерцания
bold Двойная интенсивность
civis Скрыть курсор
clear Очистка экрана
сnorm Отобразить курсор
cup Перемещение курсора на экране в позицию x, у
el Очистка до конца строки
ell Очистка к началу строки
smso Переход в режим отступа
rmso Выход из режима отступа
smul Переход в режим подчеркивания
rmul Выход из режима подчеркивания
sc Сохранение текущего положения курсора
rc Восстановление последней позиции курсора
sgr0 Обычный экран
rev Обратное видео

 

21.1.2. Числовой вывод

Наиболее распространенный числовой вывод:

Название Значение
cols Количество столбцов
it Настройка табуляции
lines Количество строк на экране

 

21.1.3. Поток вывода булевых данных

Команда tput включает незначительное количество булевых операторов.

Название Значение
chts Курсор трудно заметить
hs Имеет строку статуса

 

21.2. Работа с командой tput

 

Рассмотрим наиболее распространенные разновидности команды tput, а также методы ее использования в сценариях.

 

21.2.1. Присвоение имен командам tput

Можно использовать поток вывода всех имен команды tput, присваивая их переменным с более осмысленными наименованиями. При этом применяется следующий формат:

имя переменной='tput name'

 

21.2.2. Применение булевого потока вывода

Чтобы применить булев поток вывода для команды tput, воспользуйтесь конструкцией if:

STATUS_LINE=`tput hs` if $STATUS_LINE; then

echo "your terminal has a status line"

else

echo "your terminal has NO status line"

fi

 

21.2.3. Использование команды tput в сценариях

В приведенном сценарии командам tput bel и cl присваиваются более значимые имена.

$ pg tput1

#!/bin/sh

BELL=`tput bel`

CLEAR=`tput cl`

echo $BELL

echo $CLEAR

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

$ pg tput2

#!/bin/sh

BOLD=`tput bold`

REV=`tput rev` NORMAL=`tput sgr0` CURSOR_OFF=`'tput civis` CURSOR_ON=`tput cnorm` tput init

# сокрытие курсора, выделение текста, перестановка текста, отображение курсора

echo $CURSOR_OFF

echo "${BQLD} WELCOME TO THE PIZZA PLACE ${NORMAL}"

echo -e "\n${REV} WE ARE OPEN 7 DAYS A WEEK ${NORMAL}"

echo $CURSOR_ON

 

21.2.4. Генерирование escape–последовательностей

Обратите внимание, что при использовании эмулятора довольно затруднительно скрыть курсор. Это обусловлено несколькими причинами:

   1. Некоторые эмуляторы не воспринимают управляющий символ, скрывающий курсор. Желательно, чтобы разработчики программного обеспечения для эмуляторов учли это замечание. В исходный код потребуется внести изменения, реализующие сокрытие курсора.

   2. Существует мнение, что некоторые более ранние версии команды tput civis не функционируют должным образом.

Управляющим символом, используемым для сокрытия курсора, является ?251

(буква l). Для возврата в исходное положение применяется символ ?25h.

Все управляющие символы выполняются с помощью escape–последовательности. Обычно за кодом клавиши [Esc] следует символ [. Затем управляющая последовательность подключает или отключает определенный атрибут терминала.

Для генерирования escape–последовательностей можно воспользоваться двумя различными методами. В таблице ниже приводятся оба метода, которые зависят от имеющейся системы. Третий метод можно применить независимо от того, используется система UNIX или Linux, поскольку управляющая последовательность реализована в составе конструкции echo. Именно третий метод и применяется в книге.

Чтобы переслать escape–последовательность и отключить курсор, используется следующий код:

Linux/BSD echo -e "\033[?251"
System V echo "\033[?251"
Обобщенный метод echo "<CTRL‑VXESCAPE>[?2 51"

Клавише [Esc] соответствует код \033. Символ \ указывает команде echo, что далее следует восьмеричное значение. Например, для вывода на экран символа @ можно применить команду:

echo "@"

Или же для вывода на экран этого символа можно воспользоваться восьмеричным значением символа, которое равно 100.

echo -e "\100"

Для System V примените команду:

echo "\100"

Результат будет аналогичным.

Команда clear очищает экран и устанавливает курсор в верхнем левом углу экрана. Это положение курсора обычно называется home. При работе с терминалами, относящимися к семейству VT, эту процедуру выполняет последовательность esc [2J. Данную последовательность можно отправить с помощью конструкции echo.

System V echo "\033[2J"

LINUX/BSD echo -e "\033[2J"

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

echo ' hit the key then [?25h'

Здесь даны указания о том, что следует воспользоваться комбинацией клавиш [Ctrl+V], затем нажать клавишу [Esc] и после этого ввести символы [?25h.

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

функцию либо перевернуть пару страниц и сразу ознакомиться с результатами выполнения сценария.

$ pg cursor

#!/bin/sh

#отображение|сокрытие курсора

#отображает или скрывает курсор при работе с терминалами vt100, 200, 220, meth220

#замечание: функционирует при нормальном ttу–соединении при использовании

#некоторых win–эмуляций

#проверьте TERM env для вашего типа!

_ОРТ=$1

if [ $# -ne 1 ]; then

echo "Usage: `basename $0` cursor [on | off]"

exit 1

fi

case "$_OPT" in

on|ON|On)

# отображение курсора

ON=`echo ^[[?25h`

echo $ON

;;

off|OFF|Off)

# сокрытие курсора

OFF=`'echo ^[ [?251`

echo $OFF

;;

*)echo "Usage: cursor on | off" exit 1

;;

esac

 

21.2.5. Изменение положения курсора

Команду tput также можно применять для отображения курсора в произвольном месте экрана. При этом используется следующий формат:

cup r c

где r — это номер ряда (строки) в нижней части экрана, а с — номер столбца на экране.

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

xу()

(

#_R= row, _C=column

_R=$1

_C=$2

tput cup $_R $_C

}

clear

xy 1 5

echo -n "Enter your name :"

read NAME

xy 2 5

echo -n "Enter your age :"

read AGE

Конечно, желательно передавать строку для отображения; ниже приводится небольшая модификация этого сценария.

ху()

{

#_R= row, _C=column

_R-$1

_TEXT=$3

tput cup $_R $_C

echo -n $_TEXT

}

Сценарий можно вызвать следующим образом:

xy 5 10 "Enter your password :" read CODE

 

21.2.6. Центрирование отображаемого текста

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

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

Введите символы, нажмите клавишу [Return], и текст отобразится в середине экрана начиная со строки 10.

echo -n "input string :"

read STR

# быстрый способ вычисления длины строки

LEN=`echo $STR | wc -с`

COLS=`tput cols`

NEW_COL=`expr \($COLS - $LEN \) / 2`

xy 10 $NEW_COL

echo $STR

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

использовать текст и номер строки. Функция будет иметь следующий вид:

centertxt() {

_ROW=$1 _STR=$2

# быстрый способ получения длины строки

LEN=`echo $_STR | wc -c`

COLS=`tput cols`

_NEW_COL=`expr \{$COLS - $LEN \) / 2`

xy $_ROW $_NEW_COL

echo $_STR

}

Чтобы вызвать функцию, следует применить команду:

centertxt 15 "THE MAIN EVENT"

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

centertxt 15 $1

 

21.2.7. Определение атрибутов терминала

Рассмотрим сценарий, в котором с помощью команды tput производится обращение к базе данных terminfo. С помощью некоторых команд tput отображаются управляющие коды терминала.

$ pg termput

#!/bin/sh

#termput

#инициируйте tput для терминала

tput init

clear

echo "tput <> terminfo"

infocmp -l $TERM | while read LINE

do

case $LINE in

bel*) echo "$LINE: sound the bell" ;;

blink*) echo "$LINE: begin blinking mode";;

bold*) echo "$LINE: make it bold" ;;

el*) echo "$LINE: clear to end of line" ;;

civis*) echo "$LINE: turn cursor off";;

cnorm*) echo "$LINE: turn cursor on ";;

clear*) echo "$LINE: clear the screen" ;;

kcuul*) echo "$LINE: up arrow ";;

kcubl*) echo "$LINE: left arrow ";;

kcufl*) echo "$LINE: right arrow ";;

kcudl*) echo "$LINE: down arrow ";;

esac done

Команда infocmp извлекает из файла базы данных terminfo информацию о терминале. Если нужно просмотреть файл, содержащий сведения о настройках терминала, выполните команду:

$ infocmp $TERM

Ниже с помощью сценария termput отображается поток вывода для некоторого терминала:

$ termput

tput <> terminfo

bel=^G,: sound the bell

blink=E[5m, : begin blinking mode

bold=E[1m,: make it bold

civis=E[?251,: turn cursor off

clear=E[HE[J,: clear the screen cnorm

‑E[?25h,; turn cursor on

el=E[K,: clear to end of line

ell=E[lK,: clear to end of line

kcubl=E[D,: left arrow

kcudl=E[B,: down arrow

kcufl=E[C,: right arrow

kcuul=E[A,: up arrow

 

21.2.8. Применение функциональных клавиш при работе со сценариями

С помощью команды cat можно обращаться к специальным клавишам ([F1], [стрелка_вверх] и т. д.). Введите команду cat -v, затем нажмите любую управляющую клавишу и просмотрите, что отобразится в нижней строке. Когда просмотр завершится, нажмите комбинацию клавиш [Ctrl+C].

В следующем примере вызывается команда cat и используются клавиши [F1] (^[OP), [F2](^[OQ) и [стрелка_вверх](^[[A).

$ cat -v

^[ОР

^[OQ

^[[А

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

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

$ pg control_keys

#!/bin/sh

#управляющие_клавиши

#для вставки примените последовательность 'sequence'

uparrowkey='^[[А'

downarrowkey='^[[В'

leftarrowkey='^[[D'

rightarrowkey='^[[С'

f1key='^[OP'

f2key='^[OQ'

echo -n " Press a control key then hit return"

read KEY

case $KEY in

$uparrowkey) echo "UP arrow";;

$downarrowkey) echo "DOWN arrow";;

$leftarrowkey) echo "LEFT arrow";;

$rightarrowkey) echo "RIGHT arrow";;

$f1key) echo "F1 key";;

$f2key) echo "F2 key";;

*) echo "unknown key $key";;

esac

 

21.2.9. Применение различных цветов

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

Цвета переднего плана

Значение Цвет
30 черный
31 красный
32 зеленый
33 желтый (или коричневый)
34 голубой
35 пурпурный
36 синий (циан)
37 белый (или серый)

Фоновые цвета

Значение Цвет
40 черный
41 красный
42 зеленый
43 желтый (или коричневый)
44 голубой
45 пурпурный
46 синий (циан)
47 белый (или серый)

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

[значение_фона; значение переднего плана m

 

21.2.10. Генерирование цветов

Для генерирования цвета управляющие символы встраиваются в конструкцию echo. Данный метод применяется при работе с цветным терминалом и произвольной системой. Как и в случае с управляющими символами, цвета можно генерировать с помощью escape–последовательностей, встраиваемых в конструкцию echo. Для создания черного фона с передним планом зеленого цвета воспользуйтесь командой:

Linux/BSD: echo -e "\033[40;32m"

System V: echo "\033[40;32m"

Обобщенный метод: echo "[40;32m"

При использовании обобщенного метода, т. е. комбинации клавиш [Ctrl+V], нажмите клавишу [Esc], затем введите символы [40;32m. Обобщенный метод и применяется далее в книге.

Возможно, лучше поместить конструкции echo, отвечающие за воспроизведение цвета, в конструкцию case, а затем оформить все это в виде функции. Ниже, приводится функция цвета case.

colour ()

(

# формат цвет_фона; цвет_переднего_планаm

case $1 in

black_green)

echo '^[[40;32m';;

black_yellow)

echo '^[[40;33m';;

black_white)

echo '^[[40;37m';;

black_cyan)

echo '^[[40;36m';;

red_yellow)

echo '^[[41;33m';;

black_blue)

echo '^[[40;34m';;

esac

}

Чтобы задать красный цвет фона и желтый цвет переднего плана, примените команду:

colour red_yellow

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

colour whatever echo something

# измените на другой цвет

colour whatever

echo something

Обычно по умолчанию в качестве цветов экрана используются черный и белый. Рассмотрим, как изменить эту установку, чтобы фоновым цветом был черный, а цветом переднего плана — зеленый. С этой целью в файл .profile достаточно добавить конструкцию echo, которая создает требуемую комбинацию.

Ниже приводится пример кода для управления экраном.

$ pg colour_scr

#!/bin/sh

# colour_scr

tput init

MYDATE=`date +%D`

colour ()

{

# формат цвет_фона; цвет_лереднего_планаm

case $1 in

black_green)

echo '^[[40;32m' ;;

black_yellow)

echo '^[[40;33m' ;;

black_white)

echo '^[[40;37m' ;;

black_cyan)

echo '^[[40;36m' ;;

black_red)

echo '^[[40;31m' ;;

esac

}

xy() #xy

   • для вызова: ху строка, столбец,"text"

   • переход к координатам ху на экране {

#_R=row, _C=column

_R=$1

_C=$2

_TEXT=S3

tput cup $_R $_C

echo -n $_TEXT

}

center()

{

center

#центрирование строки текста на экране

#для вызова: center "строка" номер_строки

_STR=$1

_ROW=$2

# неудачный способ получения длины строки

LEN=`echo $_STR | wc -с`

COLS=`tput cols`

HOLD_COL=`expr $COLS - $LEN`

NEW_COL=`expr $HOLD_COL / 2`

tput cup $_ROW $NEW_COL

echo -n $_STR

}

tput clear

colour red_yellow

xy 2 3 "USER: $LOGNAME"

colour black_cyan

center "ADD A NEW WARP DRIVE TO A STAR SHIP" 3

echo -e "\f\f"

center " " 4

colour black_yeliow

xy 5 1 " "

xy 7 1 "_____"

xy 21 1 " "

center "Star Date $MYDATE " 22

xy 23 1 "____ _____________"

colour black_green

xy 6 6 "Initials :"

read INIT

xy 8 14

echo -n "Security Code No: :"

read CODE

xy 10 13

echo -n "Ship's Serial No: :"

read SERIAL

xy 12 14

echo -n "Is it on the Port Side :"

read PORT

colour red_yellow

center " Save This Record [Y..N]:" 18

read ans

# восстановление обычных цветов экрана

colour black_white

Нетрудно заметить, что этот сценарий не включает методов проверки. В данном случае все нормально. Сценарий просто демонстрирует, как можно раскрасить экран.

 

21.2.11. Улучшение внешнего вида меню

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

1: ADD A RECORD

2: VIEW A RECORD

3: PAGE ALL RECORDS

4: CHANGE A RECORD

5: DELETE A RECORD

P: PRINT ALL RECORDS

H: Help screen

Q: Exit Menu

В сценарии обработки этого меню применяется функция read_char, поэтому пользователь не должен при выборе опций меню нажимать клавишу (Return]. Для игнорирования сигналов 2, 3 и 15 применяется команда trap (более подробно это команда обсуждается далее), поэтому пользователь может не прерывать работу с меню.

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

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

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

Ниже приводится файл priv.user, содержащий имена пользователей, которые могут или не могут удалять записи и вносить в них изменения. Из приведенного текста видно, что пользователи root, dave и matty не имеют права вносить изменения в файлы баз данных, а пользователи peter и louise располагают этим правом.

$ pg priv.user

#файл доступа priv.user для меню apps

#его изменение является рискованным !!!!

#формат реализуют записи USER AMEND/DELETE

#например, запись "root yes" означает, что пользователь root может

#обновлять или удалять записи

#запись "dave no" означает, что пользователь dave не может обновлять или удалять записи

root no

dave no

peter yes

louise yes

matty no

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

user_level()

(

while read LINE

do

case $LINE in

\#*);;

*) echo $LINE >>$HOLD1 ;;

esac done < $USER_LEVELS

FOUND=false

while read MENU_USER PRIV

do

if [ "$MENU_USER"="$USER" ]; then

FOUND=true

case $PRIV in

yes|YES) return 0 ;;

no|NO) return 1 ;;

esac

else

continue

fi

done <$HOLD1

if [ "$FOUND"="false" ]; then

echo "Sorry $USER you have not been authorised to use this menu"

exit 1

fi

На следующем этапе просматривается заново отформатированный файл. Переменной FOUND присваивается значение "ложь". Теперь файл temp включает только описание имен и прав доступа; именам пользователей и правам доступа назначаются переменные. Чтобы уточнить, соответствует ли имя в файле значению user, производится проверка; значение user берется из команды whoami, расположенной в начале сценария. Если совпадение не найдено, выполняется проверка с помощью конструкции else, и с помощью команды continue обработка продолжается на следующей итерации.

Этот процесс длится до тех пор, пока все имена пользователей не будут просмотрены. При этом имя пользователя сравнивается со значением переменной USER. Если при просмотре всего файла совпадение не установлено, конструкция test в конце программного кода проверяет значение переменной FOUND. Если значением переменной является "ложь", пользователю отказывается в дальнейшей работе.

Если в процессе выполнения цикла while устанавливается искомое соответствие, переменной found присваивается значение "истина". Затем с помощью конструкции case выбираются права доступа. При этом возвращается 1 для обычных прав доступа либо 0 — для расширенных прав доступа.

Когда пользователь приступает к внесению изменений в запись или к удалению записи, выполняется проверка. Этот процесс основывается на коде возврата функции, описанном выше. В приведенном сценарии либо выполняется сортировка файла passwd, либо отображается каталог:

if user_level; then

sort /etc/passwd

else

restrict

fi

Функция restrict просто выводит на экран сообщение о нарушении прав доступа.

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

Чтобы выйти из меню, пользователь выбирает пункт q или Q, в результате чего вызывается функция для очистки экрана. Когда пользователь завершает работу с любым обширным сценарием, очень удобно в этом случае воспользоваться функцией. Это позволяет увеличить количество команд, выполняемых пользователем при завершении работы. Кроме того, значительно улучшается читабельность кода.

Ниже приводится соответствующий сценарий.

$ pg menu2

#!/bin/sh

#menu2

#СЦЕНАРИЙ ГЛАВНОГО МЕНЮ

#игнорирование CTRL‑C и прерывания QUIT

trap "" 2 3 15

MYDATE=`date +%d/%m/%Y`

THIS_HOST=`hostname -s`

USER=`whoami`

#файл, описывающий права доступа пользователя USER_LEVELS=priv.user

#файл для хранения

HOLDl=holdl.$$

#функция задания цвета

colour ()

{

# формат цвет_фона; цвет_переднего_планаm

case $1 in

black_green)

echo '^[[40;32m' ;;

black yellow)

echo '^[[40;33m' ;;

black_white)

echo '^[[40;37m' ;;

Ьlаск_суап)

echo '^[[40;36m' ;;

red_yellow)

echo '^[[41;33m' ;;

esac

}

# чтение значения клавиши

get_char()

(

#get_char

#сохранение текущих установок stty SAVEDSTTY=`stty -g`

stty cbreak

dd if=/dev/tty bs=1 count=1 2> /dev/null

stty -cbreak

# восстановление установок stty

stty $SAVEDSTTY

}

# отображение или сокрытие курсора

cursor ()

{

#cursor

#отображение/сокрытие курсора

_OPT=$1

case $_OPT in

on) echo '^[[?25h';;

off) echo '^[[?251';;

*) return 1;;

esac }

# проверка прав доступа пользователя

restrict ()

{

colour red_yellow

echo -e -n "\n\n\007Sorry you are not authorised to use this function"

colour black_green

}

user_level () {

# user level

# просмотр файла priv.user

while read LINE

do

case $LINE in

# игнорирование комментариев

\#*);;

*) echo $LINE >> $HOLD1 ;;

esac

done < $USER_LEVELS

FOUND=false

while read MENU_USER PRIV

do

if [ "$MENU_USER"="$USER" ]; then

FOUND=true

case $PRIV in

yes|YES)

return 0 ;;

no|NO)

return 1 ;;

esac

else

# соответствие не найдено, чтение следующей записи

continue

fi

done <$HOLD1

if [ "$FOUND"="false" ]; then

echo "Sorry $USER you have not been authorised to use this menu"

exit 1

fi

}

# вызывается, если пользователь выполняет выход из программы

my_exit()

{

#my_exit

#вызывается, если пользователь выбирает выход из сценария!

colour black_white

cursor on

rm *.$$

exit 0

}

tput init

# отображение на экране уровня доступа пользователя

if user_level; then

ACCESS="Access Mode ls High"

else

ACCES3="Access Mode ls Normal"

fi

tput init

while :

do

tput clear

colour black_green

cat < MAYDAY

$ACCESS

MAYDAY

colour black_cyan

echo -e -n "\tYour Choice [1,2,3,4,5,P, H,Q] >"

@ read CHOICE

CHOICE=`get_char`

case $CHOICE in

1) ls ;;

2) vi ;;

3) who ;;

if userlevel; then

ls -l |wc

else

restrict

fi ;;

5)

if userlevel; then

sort /etc/passwd

else

restrict

fi ;;

esac

echo -e -n "\tHit the return key to continue"

read DUMMY

done

Подобное меню можно вызвать с помощью команды exec (из файла profile). Пользователи не могут изменить эту последовательность действий. Такой подход распространен в случае с пользователями, работающими только с приложениями UNIX или Linux и не использующими возможности интерпретатора shell.

 

21.3. Заключение

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

 

ГЛАВА 22

 

Создание экранного ввода

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

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

В главе обсуждаются следующие темы:

   • проверка достоверности вводимых данных;

   • добавление, удаление, обновление записей и их просмотр;

   • сценарии, выполняющие обновление файлов.

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

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

Поле Длина Разрешен ввод следующей информации Описание
Staff number 10 Числовая Номер служащего по штатному расписанию
First name 20 Символьная Имя служащего
Second name 20 Символьная Фамилия служащего
Department Accounts Отдел, где работает служащий
IT
Services
Sales
Claims

Поля разделяются двоеточием (:). Например:

:::

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

Для связи различных заданий реальный сценарий должен иметь соответствующее меню или модуль. Чаще всего для этой цели используется оболочка функций, содержащихся в файле, совместно с каким‑либо меню сценария. Каждый сценарий включает команду trap; благодаря ее использованию игнорируются сигналы 2, 3 и 15.

 

22.1. Добавление записей

При добавлении записи в файл выполняются следующие задачи:

   1. Подтверждение вводимых данных.

   2. Внесение записи в файл.

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

Функция для проверки длины строки:

length_check()

{

# length_check

# $1=строка, $2= длина строки не превышает этого значения

_STR=$1

_МАХ=$2

_LENGTH=`echo $_STR | awk '{print length ($0) }"`

if [ "$_LENGTH" -gt "$_MAX" ]; then

return 1 else

return 0 fi }

Функция, выполняющая проверку наличия в строке исключительно числовых данных:

a_number ()

   • a_number

   • $1=string {

_NUM=$1

_NUM=`echo $1 awk '{if($0~/[^0-9]/) print "1")'`

if [ "$_NUM" != "" ]

then

return 1

else

return 0

fi

}

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

characters()

#characters

#$1=string

{

_LETTERS_ONLY=$1

_LETTERS_ONLY=`echo $1|awk '{if($0~/[^a‑zA‑Z]/) print "l"}'`

if [ "$_LETTERS_ONLY" != "" ]

then

return 1 else

return 0 fi

}

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

Чтобы сообщения находились на экране до тех пор, пока пользователь не нажмет на клавишу для их удаления, нужно воспользоваться командным приглашением. Следующая функция реализует командное приглашение read_a_char.

continue_promptYN() {

# continue_prompt

echo -n "Hit any key to continue.."

DUMMY=`read_a_char`

}

Когда вводятся данные пользователя, содержащие номер служащего, нужно убедиться, что ранее подобная информация не вводилась. Поле должно быть уникальным. Существует несколько способов для выполнения этой задачи; в данном случае применяется команда grep. С помощью команды grep выполняется поиск номера служащего, который содержится в строке _CODE. Если утилита awk не возвращает какого‑либо значения, то дублирующиеся значения отсутствуют и функция завершает выполнение с кодом возврата 0. Ниже приводится код этой функции. (Обратите внимание, что для нахождения точного соответствия в команде grep используется выражение "$_CODE\>". Двойные кавычки служат для сохранения значения переменной; при использовании одинарных кавычек ничего возвращаться не будет.)

check_duplicate() {

#check_duplicate

#проверка дубликата номера служащего

_CODE=$1

MATCH="grep "$_CODE\>" $DBFILE"

echo $_CODE

if [ "$MATCH"="" ]; then

return 0 # нет дублирования

else

return 1 # дубликат найден

fi

}

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

служащего. Функционирование этого программного кода объясняется дальше.

while : do

echo -n "Employee Staff Number :"

read NUM

# проверка вводимых данных

if [ "$NUM" != "" ]; then

if a_number $NUM; then

# номер OK

NUM_PASS=0

else

NUM_PASS=1

fi

if length_check $NUM 10; then

# длина OK

LEN_PASS=0

else

LEN_PASS=1

fi

# проверка наличия дубликатов…

if check_duplicate $NUM; then

# нет дубликатов

DUPLICATED=0

else

DUPLICATED=1

echo "Staff Number: There ls already an employee with this number"

continue_prompt

fi

# проверка значений всех трех переменных; все они должны выполняться

if [ "$LEN_PASS"="0" -a "$NUM_PASS"="0" -a "$DUPLICATE"="0" ]

then

break

else

echo "Staff Number: Non‑Numeric or Too Many Numbers In Field" continue_prompt

fi

else

echo "Staff Number: No Input Detected, This Field Requires a Number" continue_prompt

fi

done

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

После просмотра номера служащего проверяется, содержатся ли в поле некоторые данные:

if [ "$NUM" != "" ]

Если поле не содержит данные для ввода, не выполняется часть then конструкции if. В части else, которая завершает код подтверждения поля, отображается следующее сообщение:

Staff Number: No Input Detected, This Field Requires a Number

Часть then производит все необходимые проверки правильности вводимых данных. При условии, что имеются вводные данные, вызывается функция a_number. Эта функция проверяет, содержит ли передаваемая строка числовые данные; если это так, функция возвращает значение 0, в противном случае возвращается значение 1, На базе возвращаемых значений флагу num_pass присваивается либо значение о (успешный возврат — наличие строки из чисел), либо значение 1 (неудачный возврат -cтрока не является числовой).

Затем вызывается функция length check. Эта функция передает не только строку, но и максимальное число символов, содержащихся в строке. В данном случае передается десять символов. Если количество символов меньше или равно максимальной величине, отображается значение 0; иначе возвращается значение 1. Флаг len_PASS принимает либо значение 0 (в случае успешного возврата, когда длина строки не превышает максимального значения), либо значение 1 (при неудачном возврате, если получена строка, длина которой равна максимальному значению).

Проверим, имеются ли дубликаты для номеров служащих. Эту задачу выполняет функция check_duplicate. Если дубликаты не обнаружены, флагу DUPLICATE присваивается значение 1. Теперь нужно убедиться, что все три флага имеют значение 0 (ошибки отсутствуют). Для этого воспользуемся логической функцией and. При выполнении части then обе части уравнения должны принять истинное значение.

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

if [ "$LEN_PASS" = "0" -a "$NUM_PASS" = "0" -a "$DUPLICATE" = "0"]; then

break

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

Staff Number: Non‑Numeric or Too Many Numbers In Field

Действительно, значение одного поля подтверждается.

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

while : do

echo -n "Employee's First Name :"

read F_NAME

if [ "$F_NAME" != "" ]; then

if characters $F_NAME; then

F_NAME_PASS=0

else

F_NAME_PASS=1

fi

if length_check $F_NAME 20; then

LEN_PASS=0 else

LEN_PASS=1

fi

if [ "$LEN_PASS"="0" -a "$F_NAME_PASS"="0" ]; then

break

else

echo "Staff First Name: Non‑Character or Too Many Characters In Field"

continue_prompt

fi

else

echo "Staff First Name: No Input Detected, This Field Requires Characters" continue_prompt

fi

done

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

while :

do

echo -n "Company Department :"

read DEPART

case $DEPART in

ACCOUNTS|Accounts|accounts) break;;

SALES|Sales|sales) break;;

IT|It|it) break;;

CLAIMS|Claims|claims) break;;

SERVICES|Services|services) break;;

*) echo "Department: Accounts, Sales, IT, Claims, Services";;

esac

done

Когда все поля подтверждены, отображается приглашение с вопросом, следует ли сохранять эту запись. С этой целью применяется функция continue_promptYN, к которой мы уже обращались ранее, используя Y или N в качестве ответа. Если пользователь нажимает клавишу [Return], можно также передать ответ, заданный по умолчанию.

Если пользователь выбирает N, производится просмотр блоков кода в конструкции if и выполнение сценария завершается. При вводе пользователем большого числа записей следует применить функцию, которая добавляет записи в теле цикла while. Тогда после добавления записи возврат в меню не происходит, а выполнение сценария не завершается.

Если пользователь выбирает Y, запись сохраняется. Для добавления записи в файл применяется следующий код:

echo "$NUM:$F_NAME:$S_NAME:$DEPART" >> $DBFILE

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

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

Вот как выглядит поток данных вывода в случае добавления записи:

ADD A RECORD

Employee Staff Number : 23233

Employee's First Name : Peter

Employee's Surname : Wills

Company Department : Accounts

Do You wish To Save This Record [Y..N] [Y]:

saved

А теперь обратите внимание, какой вид имеет файл DBFILE после добавления нескольких записей:

$ pg DBFILE

32123:Liam:Croad:Claims 2399:Piers:Cross:Accounts 239192:John:Long:Accounts 98211:Simon:Penny:Services 99202:Julie:Sittle:XT 23736:Peter:Wills:Accounts 89232:Louise:Wilson:Accounts 9l811:Andy:Wools:IT

Ниже приодится полный сценарий, выполняющий добавление записи:

$ pg dbase_add

#!/bin/sh

#dbase_add

#добавление записи

#игнорирование сигналов

trap "" 2 3 15

#файл temp содержит файлы

DBFILE=DBFILE

HOLD1=HOLD1.$$

read_a_char ()

#read_a_char

#сохранение установок

SAVEDSTTY=`stty -g`

stty cbreak

dd if=/dev/tty bs=1 count=1 2> /dev/null

stty -cbreak

stty $SAVEDSTTY

}

continue_promptYN()

#вызов: continue_prompt "string to display" default_answer

{

# continue_prompt

_STR=$1

_DEFAULT=$2

# проверка наличия корректных параметров

if [ $# -lt 1 ]; then

echo "continue_prompt: I need a string to display"

return 1

fi

while :

do

echo -n "$_STR [Y..N] [$_DEFAULT]:"

read _ANS

#если пользователь нажимает клавишу ввода, устанавливается значение

#по умолчанию, затем определяется возвращаемое значение

: ${_ANS:=$_DEFAULT}

if [ "$_ANS"="" ]; then

case $_ANS in

Y) return 0 ;;

N) return 1 ;;

esac

fi

# пользователь выбирает что–либо

case $_ANS in

y|Y|Yes|YES)

return 0 ;;

n|N|No|NO)

return 1 ;;

*) echo "Answer either Y or N, default ls $_DEFAULT" ;;

esac

echo $_ANS

done

)

continue_prompt() {

# continue_prompt

echo -n "Hit any key to continue.."

DUMMY=`read_a_char`

}

length_check ()

# length_check

# $1=строка для проверки длины $2=максимальная длина

_STR=$1

_МАХ=$2

_LENGTH=`echo $_STR | awk '{print length ($0)}`

if [ "$_LENGTH" -gt "$_MAX" ]; then

return 1 else

return 0 fi

}

a_number()

(

#a_number

#вызов: a_number $1=number _NUM=$1

_NUM=`echo $1|awk '{if($0~/[^0-9]/) print "1"}'`

if [ "$_NUM" != "" ]

then

# ошибки

return 1

else

return 0

fi

}

characters()

#characters

#вызов: char_name string {

_LETTERS_ONLY=$1

_LETTERS_ONLY=`echo $1 | awk '{if ($0~/[^a‑zA‑Z]/) print "1")'`

if [ "$_LETTERS_ONLY" != "" ]

then

# ошибки

return 1

else

# содержит только символы

return 0

fi

}

check_duplicate() {

#check_duplicate

#проверка дублирования номера служащего

#для вызова: check_duplicate строка _CODE=$1

MATCH=`grep "$_CODE\>" $DBFILE`

echo $_CODE

if [ "$MATCH"="" ]; then

return 0 # нет дубликата

else

return 1 # дубликат обнаружен

fi

}

add_rec()

{

# add_rec

# == STAFF NUMBER

while :

do

echo -n "Employee Staff Number :"

read NUM

if [ "$NUM" != "" ]; then

if a_number $NUM; then

NUM_PASS=0

else

NUM_PASS=1

fi

if length_check $NUM 10; then

LEN_PASS=0

else

LEN_PASS=1

fi

# проверка наличия дубликатов… if check_duplicate $NUM; then

DUPLICATED=0

else

DUPLICATE=1

echo "Staff Number: There ls already a employee with this number" continue_prompt

fi

if [ "$LEN_PASS"="0" -a "$NUM_PASS"="0" -a "$DUPLICATE"="0" ] then

break

else

echo "Staff Number: Non‑Numeric or Too Many Numbers In Field" continue_prompt

fi

else

echo "Staff Number: No Input Detected, This Field Requires a Number" continue_prompt fi done

# == Имя

while :

do

echo -n "Employee's First Name:"

read F_NAME

if [ "$F_NAME" != "" ]; then

if characters $F_NAME; then

F_NAME_PASS=0

else

F_NAME_PASS=1

fi

if length_check $F_NAME 20; then

LEN_PASS=0

else

LEN_PASS=1

fi

# oбa условия должны быть истинными для выхода из этого цикла if [ "$LEN_PASS"="0" -a "$F_NAME_PASS"="0" ]; then

break

else

echo "Staff First Name: Non‑Character or Too Many Characters In Field" continue_prompt

fi

else

echo "Staff First Name: No Input Detected, This Field Requires Characters"

continue_prompt

fi

done

# == Фамилия

while :

do

echo -n "Employee's Surname :"

read S_NAME

if [ "$S_NAME" != "" ]; then if characters $S_NAME; then

$_NAME_PASS=0

else

$_NAME_PASS=1

fi

if length_check $S_NAME 20; then

LEN_PASS=0

else

LEN_PASS=1

fi

if [ "$LEN_PASS"="0" -a "$S_NAME_PASS"= "0" ]; then

break else

echo "Staff Surname: Non‑Character or Too Many Characters In Field" continue_prompt fi else

echo "Staff Surname: No Input Detected, This Field Requires Characters" continue_prompt fi done

# == Отдел

while :

do

echo -n "Company Department :" read DEPART case $DEPART in

ACCOUNTS|Accounts|accounts) break;;

SALES|Sales|sales) break;; IT|It|it) break;;

CLAIMS|Claims|claims) break;;

Services|SERVICES|services) break;;

*) echo "Department: Accounts, Sales, IT, Claims, Services";; esac done )

# основная программа

clear

echo -e "\t\t\tADD A EMPLOYEE RECORD"

if [ -s $DBFILE ]; then :

else

echo "Information: Creating new file to add employee records"

>$DBFILE fi

add_rec if continue_promptYN "Do You wish To Save This Record " "Y"; then

echo "$NUM:$F_NAME:$S_NAME:$DEPART" >> $DBFILE

echo "record saved"

sleep 1

sort +2 -t: $DBFILE > $HOLD1 2> /dev/nuil

if [ $? -ne 0 ]; then

echo "problems trying to sort the file..check it out"

exit 1 fi

mv $HOLD1 $DBFILE if [ $? -ne 0 ]; then

echo "problems moving the temp sort file..check it out"

exit 1 fi

else

echo " record not saved"

sleep 1 fi

 

22.2. Удаление записей

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

При удалении записи выполняются следующие операции:

   1. Поиск записи.

   2. Отображение записи.

   3. Подтверждение процедуры удаления.

   4. Обновление файла.

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

обработке. Можно применить команду grep или утилиту awk. Но поскольку данный файл, скорее всего, содержит не более 100 записей, просмотрим его и определим наличие совпадений.

Если же файл содержит более двух сотен записей, желательно воспользоваться утилитой awk. Это значительно быстрее, чем просмотр файла; к тому же утилита awk более удобна при разделении полей, принадлежащих переменным.

Чтобы применить команду grep или утилиту awk, можно выполнить поиск в файле DBFILE:

echo "enter the surname to search "

read STR

#при работе с awk используйте команду

awk -F: '/$STR/' DBFILE

#при работе с grep используйте команду

grep "$STR" DBFILE

#либо команду

grep "$STR\ >" DBFILE

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

Чтобы разделить поля, можно каждому полю назначить переменные (не забывайте, что разделителем полей служит двоеточие). Переменной Ifs нужно присвоить значение двоеточия. Если не сделать этого, запись нельзя будет просматривать. При изменении значения переменной ifs желательно сначала сохранить установки. Благодаря этому их можно будет восстановить по завершении работы сценария.

Чтобы сохранить значения переменной ifs, примените следующую команду:

SAVEDIFS=$IFS

Заменить значение переменной ifs двоеточием можно, выполнив команду

IFS=:

По завершении работы с переменной IFS вы можете легко восстановить ее значение:

IFS=$SAVEDIFS

С помощью функции getrec можно выполнить полномасштабный поиск; этой функции не передаются параметры.

get_rec () {

# get_rec

clear

echo -n "Enter the employee surname :"

read STR

if [ "$STR"="q" ]; then

return 1 fi

REC=0

MATCH=no

if [ "$STR" != "" ]; then

while read CODE F_NAME S_NAME DEPART

do

REC=`expr $REC + 1` tput cup 3 4

echo -n " searching record.. $REC"

if [ "$S_NAME"="$STR" ]; then

MATCH=yes

display_rec

break

else

continue

fi

done

else

echo "Enter a surname to search for or q to quit"

fi

if [ "$MATCH"="no" ]; then

no_recs

fi

}

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

if [ "$STR"! = "" ]; then

Но не такой:

[ -z $STR ]

При выборе первой проверки пользователю достаточно нажать на клавишу [Return], чтобы выполнить команду trap. Во втором случае устанавливается лишь наличие строки нулевой длины.

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

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

if continue_promptYN "Do You Wish To DELETE This Record" "N"; then

echo "DEL"

grep -v $STR DBFILE >$HOLD1 2> /dev/null

if [ $? -ne 0 }; then

echo "Problems creating temp file $HOLD1.. check it out"

exit 1 fi

Удаление записи выполняется выполнением команды grep с опцией -v. В этом случае с помощью строки STR отображаются все несовпадающие поля. (Эта строка содержит фамилию, которая запрашивается пользователем при удалении записи.)

Поток данных вывода для команды grep перенаправляется во временный файл, где выполняется сортировка. Затем временный файл заменяет исходный файл DBFILE.

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

Enter the employee surname :Wilson

searching record,. 6

EMPLOYEE NO: 69232 FIRST NAME : Louise SURNAME : Wilson DEPARTMENT : Accounts

Do You Wish To DELETE This Record [Y..N] [N] :

А теперь приведем полный сценарий, выполняющий удаление записи:

$ pg dbase_del

#!/bin/sh

#dbase_del

#удаление записи

#перехват сигналов

trap "" 2 3 15

#Файл данных

DBFILE=DBFILE

#временные файлы

HOLD1=HOLD1.$$

HOLD2=HOLD2.$$

continue_promptYN(} {

#continue_prompt

_STR=$1

_DEFAULT=$2

#проверим наличие правильных параметров

if [ $# -lt 1 ]; then

echo "continue_prompt: I need a string to "display"

return 1

fi

while : do

echo -n "$_STR [Y..N] [$_DEFAULT]:"

read _ANS

: ${_ANS:=$_DEFAULT}

if [ "$_ANS"="" ]; then

case $_ANS "in

Y) return 0 ;;

N) return 1 ;;

esac

fi

case $_ANS in

у|Y|Yes|YES)

return 0;;

n|N|No|NO)

return 1;;

*) echo "Answer either Y or N, default is $_DEFAULT"

esac

done

}

display_rec()

{

#display_rec

#можно воспользоваться командой cat << документ

tput cup 5 3

echo "EMPLOYEE NO: $CODE" echo "FIRST NAME : $F_NAME" echo "SURNAME :$S_NAME"

echo "DEPARTMENT : $DEPART"

echo -e "\n\n"

}

no_recs () {

# no_recs

echo -e "\n\nSorry could not find a record with the name $STR"

}

get_rec () {

# get_rec

clear

echo -n "Enter the employee surname :"

read STR

if [ "$STR"="q" ]; then

return 1

fi

REC=0

MATCH=no

if [ "$STR" != "" ]; then

while read CODE F_NAME S_NAME DEPART

do

REC=`expr $REC + 1`

echo -n " searching record.. $REC"

if [ "$S_NAME" = "$STR" ]; then

MATCH=yes

display_rec

break

else

continue

fi

done << $DBFILE

else

echo "Enter a surname to search for or q to quit"

fi

if [ "$MATCH"="no" ]; then

no_recs

fi

}

SAVEDIFS=$IFS

IFS=:

get_rec

if [ "$MATCH"="yes" ]; then

if continue_promptYN "Do You Wish To DELETE This Record" "N"; then echo "DEL"

grep -v $STR DBFILE >$HOLD1 2> /dev/null if [ $? -ne 0 ]; then

echo "Problems creating temp file $HOLD1..check it out" exit 1 fi

mv $HOLD1 DBFILE if [ $? -ne 0 ]; then

echo "Problems moving temp file..check it out" exit 1 fi

# сортировка файла после изменений

sort +2 -t: $DBFILE >$HOLD2 2> /dev/null if [ $? -ne 0 ]; then

echo "problems trying to sort the file..check it out"

exit 1 fi

mv $HOLD2. $DBFILE if [ $? -ne 0 ]; then

echo "problems moving the temp sort file..check it out"

exit 1 fi else

echo "no deletion"

# удаление отсутствует

fi # если нужно удалить

fi # если совпадает

# восстановление установок IFS IFS=$SAVEDIFS

 

22.3. Обновление записей

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

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

: [переменная, заданная по умолчанию:=переменная)

Пользователь может просто нажать клавишу [Return] для тех полей, значения которых изменять нежелательно. Затем переменная temp заменяется значением, заданным по умолчанию. Для выполнения произвольных обновлений можно просто вводить в соседние поля новые значения.

echo -n -e "EMPLOYEE NO: $C0DE\n"

echo -n "FIRST NAME : [$F_NAME] >"

read _F_NAME

: ${_FNAME:=$P_NAME}

Для реального обновления файла достаточно снова воспользоваться командой grep с опцией -v. Все записи обновляются по отдельности и перенаправляются во временный файл. Номер служащего задается в виде строки команды grep:

grep ~v $C0DE $DBFILE >$HOLD1

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

echo "$CODE:$_F_NAME:$_S_NAME:$_DEPART" >> $HOLD1 mv $HOLD1 $DBFILE

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

Enter the employee surname :Penny

searching record.. 7

EMPLOYEE NO: 98211

FIRST NAME : Simon

SURNAME : Penny

DEPARTMENT : Services

ls this the record you wish to amend [Y..N] [Y]:

amending

EMPLOYEE NO: 98211

FIRST NAME : [Simon] >

SURNAME : [Penny] >

DEPARTMENT : [Services] >Accounts Ready to save this record [Y..N] [Y] :

Полный сценарий, выполняющий обновление записей:

$ pg dbasechange

#!/bin/sh

# dbasechange

   • обновление записи

   • игнорирование сигналов trap "" 2 3 15

   • временные файлы DBFILE=DBFILE HOLD1=HOLD1.$$ HOLD2=HOLD2.$$

continue_promptYN()

{

   • continue_jprompt _STR=$1 _DEFAULT=$2

   • проверим, что параметры верны if [ $# -lt 1 ];. then

echo "continue_prompt: I need a string to display" return 1 fi

while : do

echo -n "$_STR [Y..N] [$_DEFAULT]:"

read _ANS

: ${_ANS:=$_DEFAULT}

if [ "$_ANS" = "" ]; then

case $_ANS in

Y) return 0 ;;

N) return 1 ;;

esac fi

case $_ANS in y|Y|Yes|YES)

return 0;;

n|N|No|NO) return 1;;

*) echo "Answer either Y or N, default is $_DEFAULT";;

esac

done }

display_rec() {

   • отображение_записи

   • можно применить команду cat « документ, но нежелательно tput cup 5 3

echo "EMPLOYEE NO: $CODE" echo "FIRST NAME : $F_NAME" echo "SURNAME : $S_NAME" echo "DEPARTMENT : $DEPART" echo -e "\n\n" }

no_recs()

{

# no_recs

echo -e "\n\nSorry could not find a record with the name $STR" }

get_rec ()

{

# get_rec

clear

echo -n "Enter the employee surname :"

read STR

if [ "$STR"="q" ]; then

return 1 fi

REC=0

MATCH=no

if [ "$STR" != "" ]; then

while read CODE F_NAME S_NAME DEPART do

REC=`expr $REC + 1` tput cup 3 4

echo -n " searching record.. $REC" if [ "$S_NAME"="$STR" ); then

MATCH=yes

display_rec

break

else

continue

fi

done

else

echo "Enter a surname to search for or q to quit"

fi

if [ "$MATCH"="no" ]; then

no_recs

fi

# основная программа

SAVEDIFS=$IFS

IFS=: get_rec

if [ "$MATCH" = "yes" ]; then

if continue_promptYN "Is this the record you wish to amend" "Y"

then

echo "amending"

# нельзя изменить код служащего .

echo -n -e "EMPLOYEE NO: $CODE\n"

echo -n "FIRST NAME : [$F_NAME] >"

read _F_NAME

: ${_FNAME:=$F_NAME}

echo -n "SURNAME : [$S_NAME] >"

read _S_NAME

: ${_S_NAME:=$S_NAME}

echo -n "DEPARTMENT : [$DEPART] >"

read _DEPART

: ${_DEPART:=$DEPART}

grep -v $CODE $DBFILE >$HOLD1

if [ $? -ne 0 ]; then

echo "Problems creating temporary file..check it out"

exit 1

fi

if continue_promptYN "Ready to save this record" "Y"; then

echo "$CODE:$_F_NAME:$_S_NAME:$_DEPART" >> $HOLD1

mv $HOLD1 $DBFILE

if [ $? -ne 0 ]; then

echo "Problems moving temporary file…check it out"

fi

echo " Record Amended" # сортировка файла после изменений sort +2 -t: $DBFILE >$HOLD2 2> /dev/null

if [ $? -ne 0 ]; then

echo "problems trying to sort the file..check it out"

exit 1

fi

mv $HOLD2 $DBFILE if [ $? -ne 0 ]; then

echo "problems moving the temp sort file..check it out"

exit 1

fi

else

#если обновление прерывается

echo "Amend aborted"

exit 0

fi

else

# если не выполняется обновление

echo "no amending"

# нет удаления

fi

# если желательно удалить

fi

# если имеется совпадение

IFS=$SAVEDIFS

 

22.4. Просмотр записей

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

if [ "$STR"="all" ]; then

echo "Surname Name Employee Code"

echo "______________________________"

cat $DBFILE | awk -F: '{print $2"\t"$3"\t\t"$1}' | more

return 0

fi

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

pg << MAYDAY

RECORD No : $REC

EMPLOYEE NUMBER : $CODE

EMPLOYEE NAME : $F_NAME

EMPLOYEE SURNAME : $S_NAME

EMPLOYEE DEPARTMENT : $DEPART

MAYDAY

Вот как выглядит поток вывода при просмотре записи:

Enter the employee surname to view or all for all records:Wilson searching record… 8

EMPLOYEE NO 89232
FIRST NAME Peter
SURNAME Wilson
DEPARTMENT IT

Do You Wish To Print This Record [Y..N] [N] :

Полный сценарий, позволяющий просматривать записи, имеет следующий вид:

$ pg dbaseview

#!/bin/sh

#dbaseview

#просмотр записей

#игнорирование сигналов trap "" 2 3 15

#временные файлы

HOLD1=HOLD1.$$

DBFILE=DBFILE

continue_promptYN ()

{

#continue_prompt

_STR=$1

_DEFAULT=$2

#проверим, что параметры верны

if [ $# -lt 1 ]; then

echo "continue_prompt: I need a string to display"

return 1

fi

while :

do

echo -n "$_STR [Y..N] [$_DEFAULT]:"

read _ANS

: $'{_ANS:=$_DEFAULT)

if [ "$_ANS"="" ]; then

case $_ANS in

Y) return 0 ;;

N) return 1 ;;

esac

fi

case $_ANS in

у|Y|Yes|YES)

return 0;;

n|N|No|NO)

return 1;;

*) echo "Answer either Y or N, default ls $_DEFAULT";;

esac

done

}

display_rec() {

#diaplay_rec

#можно применить команду cat <<.

tput cup 5 3

echo "EMPLOYEE NO: $CODE"

echo "FIRST NAME : $F_NAME"

echo "SURNAME : $S_NAME"

echo "DEPARTMENT : $DEPART"

echo -e "\n\n"

}

no_recs () {

# no_recs

echo -e "\n\nSorry could not find a record with the name $STR"

}

get_rec () {

# get_rec

clear

echo -n "Enter the employee surname to view or all for all records:"

read STR

if [ "$STR"="q" ] ; then

return 1 fi

if [ "$STR"="all" ]; then # просмотр всех записей

echo "Surname Name Employee Code" echo"____________________________________"

cat $DBFILE |awk -F: '{print $2"\t"$3"\t\t"$1}' | more

return 0

fi

REC=0

MATCH=no

if [ "$STR" != "" ]; then

while read CODE F_NAME S_NAME DEPART

do

REC=`expr $REC + 1`

tput cup 3 4

echo -n " searching record.. $REC" if [ "$S_NAME"="$STR" ]; then

# обнаружено имя

MATCH=yes

display_rec

break

else

continue

fi

done <$DBFILE

else

echo "Enter a surname to search for or q to quit"

fi

if [ "$MATCH"="no" ]; then

no_recs

fi

}

# главная программа

SAVEDIFS=$IFS

IFS=:

get_rec

if [ "$MATCH"="yes" ]; then

if continue_promptYN "Do You Wish To Print This Record" "N"; then

lpr << MAYDAY

RECORD No: $REC

EMPLOYEE NUMBER : $CODE

EMPLOYEE NAME : $F_NAME

EMPLOYEE SURNAME : $S_NAME

EMPLOYEE DEPARTMENT : $DEPART

MAYDAY

else

echo "No print of $S_NAME"

# не отображается fi # если желательно отображение

fi # если имеется совпадение

IFS=$SAVEDIFS

 

22.5. Заключение

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

В среде программистов известно довольно старое изречение. Звучит оно примерно так: "Мусор на входе, мусор на выходе… кого это заботит, только б не было слишком поздно". Это означает, что если не проверять входные данные сценариев, в поток вывода может попасть ненужная информация.

 

ГЛАВА 23

 

Отладка сценариев

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

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

В этой главе рассматриваются следующие темы: — распространенные ошибки; — применение команды set.

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

Чаще всего при написании сценариев пропускаются кавычки либо ключевое слово fi в конце конструкции if.

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

 

23.1. Наиболее распространенные ошибки

 

23.1.1. Ошибки, связанные с циклом

Если сообщение об ошибке появляется при выполнении конструкций for, while, until или case, то это может означать, что фактический блок инструкций некорректно определен. Возможно, было пропущено зарезервированное слово, требуемое в данной ситуации.

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

syntax error near unexpected token 'done' line 31: 'done'

 

23.1.2. Как обычно пропускают кавычки

Второй распространенной ошибкой является элементарный пропуск кавычек. Обратите внимание на приведенный пример. Обычно приходится сталкиваться с большим количеством подобных примеров. Можно посоветовать еще раз изучить сценарий и проконтролировать наличие всех необходимых открывающих и закрывающих кавычек — unexpected EOF while looking for '""' line 36: syntax error

Если требуется настроить отображение сообщения об ошибке, содержащего номер ошибочной строки, то в этом случае обычно применяется опция set nu текстового редактора vi. Это удобно, если просмотр файлов осуществляется с помощью редактора vi. Для настройки отображаемых сообщений откройте окно редактора vi, затем нажмите клавишу [Esc] и введите двоеточие. После этого выполните команду set nu и нажмите клавишу [Return]. В результате этого происходит нумерация строк и можно перейти к той строке, где, по сообщению интерпретатора shell, содержится ошибка.

 

23.1.3. Проверка на наличие ошибки

Другой распространенной ошибкой является неправильное применение конструкции -eq. Обычно забывают указать число с какой‑либо стороны уравнения.

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

[: missing '] '

 

23.1.4. Регистр символов

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

 

23.1.5. Циклы for

При работе с циклом for пользователи иногда забывают в части списка указать знак доллара. В результате список воспринимается как строка.

 

23.1.6. Команда echo

При отладке сценариев чрезвычайно удобно применять команду echo. Добавьте команду echo в наиболее существенных частях сценария, где могут возникнуть какие‑либо затруднения. Например, воспользуйтесь командой echo до и после считывания или изменения значения переменной.

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

 

23.2. Команда set

При отладке сценария можно использовать команду set. Ниже приведены наиболее часто применяемые отладочные опции команды set.

set -n Считывание, но не выполнение команд set -v Отображение всех строк при считывании set -х Отображение всех команд и их аргументов

Чтобы отключить опцию set, просто замените знак — на знак +. Конечно, привычнее было бы, наоборот, знак + применять для подключения, а знак — использовать для отключения. Но здесь все зависит от привычки.

Команду set можно запустить, начиная с верхней части сценария, и завершить ее выполнение по завершении сценария. Или же можно активизировать эту команду при выполнении определенной блочной конструкции, которая содержит ошибки.

Рассмотрим, как функционирует команда set. Ниже приводится небольшой сценарий, который включает в список переменных определенные имена. Пользователь вводит имя, затем с помощью цикла for просматривается список в поисках соответствия. Обратите внимание, что опция set -x применяется в верхней части сценария, а в нижней части сценария ее действие завершается.

$ pg error

#!/bin/sh

#error

#установка set -x set -x

LIST="Peter Susan John Barry Lucy Norman Bill Leslie"

echo -n "Enter your Name :"

read NAME

for LOOP in $LIST

do

if [ "$LOOP" = "$NAME" ]; then

echo "you're on the list, you're in"

break

fi

done

# отмена установки

set -x

set +x

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

$ error

error

+ error

+ LIST=Peter Susan John Barry Lucy Norman Bill Leslie

+ echo -n Enter your Name :

Enter your Name :+ read NAME

Harry

- [ Peter=Harry ]

+ [ Susan=Harry ]

+ [ John=Harry ]

+ [ Barry=Harry ]

+ [ Lucy=Harry ]

- [ Norman=Harry ]

+ [ Bill=Harry ]

+ [ Leslie=Harry ]

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

 

23.3. Заключение

При поиске ошибок нужно самостоятельно просматривать сценарии и применять

команду set наряду с набором конструкций echo. ,

 

ГЛАВА 24

 

Встроенные команды интерпретатора shell

В предыдущих главах нам уже встречались конструкции, встроенные в интерпретатор shell Напомним, что речь идет о командах, которые не находятся в каталоге /bin или usr/bin, а встроены в интерпретатор Bourne shell. Скорость выполнения встроенных команд выше по сравнению с их эквивалентами в системе (если таковые имеются), В этой главе рассматривается единственная тема: список стандартных встроенных команд интерпретатора Bourne shell.

Например, для выполнения одних и тех же действий можно воспользоваться как системными командами cd и pwd, так и аналогичными командами, встроенными в интерпретатор shell. Чтобы выполнять системную версию команды, кроме ее имени укажите и каталог, в котором она находится:

/bin/pwd

 

24.1. Полный список команд, встроенных в интерпретатор shell

 

В табл. 24.1 содержится полный перечень стандартных встроенных команд. Таблица 24.1. Стандартные встроенные команды

: Нуль, всегда возвращает истинное значение
. Считывание файлов из текущего интерпретатора shell
break Применяется в конструкциях for, while, until, case
cd Изменяет текущий каталог
continue Продолжает цикл, начиная следующую итерацию
echo Записывает вывод в стандартный поток вывода
eval Считывает аргумент и выполняет результирующую команду
exec Выполняет команду, но не в этом интерпретаторе shell
exit Выход из интерпретатора shell
export Экспортирует переменные, вследствие чего они доступны для текущего интерпретатора shell
pwd Отображает текущий каталог
read Просматривает строку текста из стандартного потока
readonly Превращает данную переменную в переменную "только для чтения"
return Выход из функции с отображением кода возврата
set Управляет отображением различных параметров для стандартного потока вводных данных
shift Смещает влево командную строку аргументов
test Оценивает условное выражение
times Отображает имя пользователя и системные промежутки времени для процессов, которые выполняются с помощью интерпретатора shell
trap При получении сигнала выполняет определенную команду
type Интерпретирует, каким образом интерпретатор shell применяет имя в качестве команды
ulimit Отображает или устанавливает ресурсы интерпретатора shell
umask Отображает или устанавливает режимы создания файлов, заданные по умолчанию
unset Удаляет из памяти интерпретатора shell переменную или функцию
wait Ожидает окончания дочернего процесса и сообщает о его завершении

Изучим более подробно некоторые команды, которые до сих пор не рассматривались либо рассматривались поверхностно.

 

24.1.1. Команда pwd

Эта команда отображает текущий каталог:

$ pwd

/tmp

 

24.1.2. Команда set

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

Формат команды:

set параметр1 параметр2..

В следующем примере параметрам присваиваются значения accounts.doc, и accounts.bak. Затем в сценарии по этим параметрам выполняется цикл.

$ pg set_ex

#!/bin/sh

set accounts.doc accounts.bak

while [ $# != 0 ]

do

echo $1

shift

done

$ set_ex

accounts.doc accounts.bak

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

 

24.1.3. Команда times

Команда times иннформирует пользователя о том, сколько времени требуется для выполнения пользовательских и любых системных команд. В первой строке указывается время, которое необходимо интерпретатору shell, а во второй — время, которое нужно всем исполняемым командам. Ниже приводится пример потока вывода, который получен с помощью команды times.

$ times

0m0.10s 0m0.13s 0m0.49s 0m0.36s

Эта команда применяется довольно часто!

 

24.1.4. Команда type

Команда type позволяет выяснить, содержится ли некоторая команда в системе, и определить тип данной команды. Команда type также сообщает, является ли название команды действительным и где именно в системе находится эта команда. Ниже приводятся примеры использования команды type:

$ type mayday

type: mayday: not found

$ type pwd

pwd ls a shell builtin

$ type times

times ls a shell builtin

$ type cp

cp is /bin/cp

 

24.1.5. Команда ulimit

Команда ulimit используется для задания и отображения предельных значений, применяемых при выполнении сценария. Обычно эта команда находится в файле /etc/profile, но вы можете использовать ее для своих нужд из текущего интерпретатора shell либо из вашего файла .profile. Общий формат команды:

ulimit опции

Ниже приводится несколько опций команды ulimit; здесь рассматриваются наиболее часто применяемые опции:

Опция Значение
-a Отображает текущие ограничения
-c Ограничивает размер дампов ядра
-f Ограничивает N блоками размер выходного файла, который создается исполняемым процессом

Ниже указаны значения, полученные при выполнении команды ulimit:

$ ulimit -a

core file size (blocks) 10000OO

data seg size (kbytes) unlimited

file size (blocks] unlimited

max memory size (kbytes) unlimited

stack size (kbytes) 8192

cpu time (seconds] unlimited

max user processes 256

pipe size (512 bytes) 8

open files 256

virtual memory (kbytes) 2105343

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

$ ulimit -с 0

$

$ ulimit -a

core file size (blocks) 0

data seg size (kbytes) unlimited

file size (blocks) unlimited

max memory size (kbytes] unlimited

stack size (kbytes) 8192

cpu time (seconds) unlimited

max user processes 256

pipe size (512 bytes) 8

open files 256

virtual memory (Kbytes) 2105343

 

24.1.6. Команда wait

Команда wait применяется для ожидания завершения одного из дочерних процессов. Команду wait можно определить с помощью процесса ID. Если этого не сделать, ожидание будет длиться до завершения всех дочерних процессов.

Формат команды ожидания завершения всех дочерних процессов:

$ wait

 

24.2. Заключение

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