Главная » Программирование для UNIX » Циклы while и until: организация поиска

0

В главе 3 для выполнения некоторого количества повторяющихся программ применялся цикл for. Обычно  цикл for просматривает список имен  файлов (например, for  i in   *.c) или  все аргументы программы оболочки (for    i in    $*).  Но  циклы оболочки могут  применяться не только для  решения таких задач, – посмотрите на цикл for  в программе which.

Существует три вида циклов: for, while и until. Безусловно, самым распространенным из них является for. Он выполняет набор команд – тело цикла – один  раз  для каждого элемента из множества слов  (чаще всего это имена файлов). Циклы while и until выполняют команды тела  цикла на  основе  анализа кода  завершения  команды. В операторе while  тело  цикла  выполняется до  тех  пор,  пока  команда-условие  не вернет ненулевой код;  для оператора until  – пока  команда-условие не вернет нулевой код. Циклы while и until идентичны, если  не считать различий в интерпретации кода завершения команды.

Базовая форма каждого из  описанных циклов  выглядит следующим образом:

for  i in  список слов

do

done

тело цикла, $i  устанавливается в следующий элемент списка

for  i    (Подразумевается список всех аргументов командного файла, т. е. $*)

do

done

тело цикла, $i  устанавливается в следующий аргумент

while  команда

do

done

тело цикла выполняется, пока команда возвращает true

until      команда

do

done

тело цикла выполняется, пока команда возвращает false

Вторая форма for, в которой под пустой строкой подразумевается $*, представляет собой удобную краткую запись для постоянной работы.

В качестве команды-условия, управляющей  циклом while  или  until, может выступать  любая команда. Приведем тривиальный  пример – цикл while, следящий за входом пользователя (скажем, Мэри) в систе му:

while  sleep  60 do

done

who  | grep  mary

Команда sleep, создающая паузу длиной 60 секунд, обычно  выполняется  всегда  (до тех пор, пока  не будет  прервана), следовательно, возвращает «успех», так  что  цикл раз  в  минуту будет  проверять, заре гистрировалась ли Мэри.

Недостаток этой  версии заключается в том,  что если  Мэри  уже  заре гистрирована, вы сможете узнать об этом только через  60 секунд. И если Мэри продолжает работать в системе, раз в минуту будут поступать сообщения об этом. Можно вывернуть цикл «наизнанку» и переписать его  при  помощи оператора until,  чтобы  получать информацию один раз, без задержки – в случае, если Мэри сейчас в системе:

until  who  |  grep  mary do

done

sleep 60

Это более интересное условие. Если  Мэри в системе, то who  | grep  mary выводит ее данные в  списке who  и возвращает «истину», потому что grep возвращает код,  указывающий, найдено ли что-нибудь, а кодом  завершения конвейера  является код завершения его  последнего элемента.

Теперь завершим эту команду, дадим ей название и проинсталлируем ее:

$ cat  watchfor

# watchfor:  следит за  входом пользователя  в  систему PATH=/bin:/usr/bin

case  $# in

0)    echo ‘Usage:  watchfor  person’  1>&2;  exit 1 esac

until  who  |  egrep  "$1" do

done

sleep 60

$ cx  watchfor

$ watchfor  you

you       tty0      Oct      1 08:01       Работает

$ mv  watchfor    /usr/you/bin       Инсталлируем ее

$

Команда grep заменена на egrep, поэтому можно ввести

$ watchfor  ‘joe|mary’

чтобы  отслеживать сразу нескольких пользователей.

Более сложный пример: будем  отслеживать вход в систему и выход из нее всех пользователей и выводить отчет о «движении» пользователей – получится нечто  вроде  расширенной команды who. Базовая структура очень проста: раз в минуту запускать who, сравнивать ее выходные данные  с ее же  данными минуту назад и печатать информацию о любых отличиях.  Выходные данные who  будут  храниться в  файле,  расположенном в каталоге /tmp. Чтобы отличать наши файлы от файлов, принадлежащих другим процессам, в имена файлов включена переменная оболочки $$  (идентификатор  процесса команды);  это  общепринятое соглашение. В имени временного файла зашифровано название соответствующей  команды –  это  удобно  для  системного администратора. Команды (в том числе  и эта  версия watchwho) часто  оставляют ненужные  файлы в /tmp, поэтому важно иметь возможность определить, ка кая именно команда так поступает.

$ cat  watchwho

# watchwho: следит за  тем,  кто входит в систему и  выходит из нее

PATH=/bin:/usr/bin new=/tmp/wwho1.$$ old=/tmp/wwho2.$$

>$old             # создать  пустой файл

while  :         #  вечный  цикл do

who  >$new

diff $old  $new mv  $new $old sleep 60

done | awk ‘/>/ { $1 = "in:       "; print }

/</ { $1 = "out:     "; print }’

Команда : встроена в оболочку, единственное, что она умеет  делать – это  оценивать  свои  аргументы и  возвращать «истину». Вместо  нее можно было  использовать команду true,  которая просто  возвращает код  завершения  «истина». (Существует и команда false.) Но  :  более эффективна, чем true, так  как она не выполняет команду из файловой системы.

В выводе diff  использованы <  и  >,  чтобы  различать данные из  двух  файлов; программа  awk обрабатывает эту  информацию и сообщает  об изменениях в более доступном для  понимания формате. Обратите вни мание на то,  что весь цикл while соединяется с awk, образуя конвейер (а можно было бы запускать awk отдельно, раз в минуту). Для такой обработки нельзя использовать команду sed, поскольку ее вывод всегда  следует за строкой ввода: всегда  есть строка ввода, которая обрабатывается, но не выводится, так возникает ненужная задержка.

Файл old создается пустым, поэтому первым выводом команды watch– who является список пользователей, находящихся в системе в настоящий  момент. Если  заменить команду, которая первоначально создает old, на  who   >$old,  то  watchwho будет  печатать только  отличия; выбор  здесь – дело вкуса.

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

$ cat  checkmail

# checkmail:  следит за  увеличением  размера почтового  ящика

PATH=/bin:/usr/bin

MAIL=/usr/spool/mail/`getname`    #  зависит  от  системы t=${1–60}

x="`ls  –l  $MAIL`" while  :

do

y="`ls  –l  $MAIL`" echo $x  $y

x="$y" sleep $t

done | awk ‘$4  < $12 { print  "Вам пришла  почта" }’

$

Снова  мы прибегли к команде awk, на этот раз для того, чтобы  обеспечить  вывод сообщения только в случае увеличения почтового ящика, а не просто  при  его изменении. В противном случае сообщение выводи-

лось бы и после удаления сообщения. (Этот изъян есть у версии, встро енной  в оболочку.)

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

$ checkmail  30

Переменная оболочки t устанавливается в заданное значение (если оно введено) или в 60, если значение не задано, посредством строки

t=${1–60}

В ней используется еще одно свойство оболочки.

Обозначение ${var} эквивалентно $var, оно удобно для  того, чтобы   избежать неприятностей с переменными внутри строк, содержащих буквы или цифры:

$ var=hello

$ varx=goodbye

$ echo $var

hello

$ echo $varx

goodbye

$ echo ${var}x

hellox

$

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

$ echo ${var?}

hello                                                           Все хорошо; var  определена

$ echo ${junk?}

junk:  parameter not  set       Стандартное сообщение (по умолчанию)

$ echo ${junk?error!}

junk:  error!                                    Выведено заданное сообщение

$

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

Еще одна возможная форма данного обозначения выглядит так: ${var– thing}, это  выражение имеет   значение $var,  если  var  определена, и thing, если не определена. Выражение ${var=thing} вычисляется аналогично, но оно еще присваивает $var значение thing:

$ echo ${junk-‘Hi there’}

Hi there

$ echo ${junk?}

junk:  parameter not  set             junk  не изменен

$ echo ${junk=’Hi  there’}

Hi there

$ echo ${junk?}

Hi there                                              junk  установлен в  Hi  there

$

Правила вычисления переменных приведены в табл. 5.3.

Таблица 5.3. Вычисление переменных оболочки

Переменная

Значение

$var

${var}

${var–thing}

${var=thing}

${var?message}

${var+thing}

значение var; если var не определена, то ничего

аналогично; удобно использовать, если за именем переменной следует буквенно-цифровое выражение

значение  var,  если  она   определена;  в  противном  случае

thing; $var не изменяется

значение  var,  если  она   определена;  в  противном  случае

thing, $var устанавливается в thing

значение var, если она определена.  Иначе вывести message и выйти из оболочки. Если сообщение пусто, вывести:

var: parameter  not  set

thing, если var определена, иначе ничего

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

t=${1–60}

t устанавливается в $1, а если аргумент не задан, то в 60.

Упражнение 5.9.  Посмотрите на реализацию команд true и false в каталоге /bin или /usr/bin. (Как их найти?) ~

Упражнение 5.10.  Измените команду watchfor так, чтобы  при  задании нескольких аргументов они воспринимались как разные пользователи (чтобы  не приходилось вводить ‘joe|mary’). ~

Упражнение 5.11.  Напишите версию watchwho,  которая использовала бы comm, а не awk, для сравнения старых и новых данных. Какую из версий вы предпочитаете? ~

Упражнение 5.12.  Напишите версию watchwho, которая хранила бы выходные данные who не в файлах, а в переменных оболочки. Какая версия вам больше нравится? Какая работает быстрее? Следует ли командам watchwho и checkmail  выполнять & автоматически? ~

Упражнение 5.13.  В чем отличие команды оболочки : (которая ничего не делает) от символа комментария #? Действительно ли нужны оба? ~

Источник: Керниган Б., Пайк Р., UNIX. Программное окружение. – Пер. с англ. – СПб: Символ-Плюс, 2003. – 416 с., ил.

По теме:

  • Комментарии