Книга: UNIX — универсальная среда программирования

5.3 Циклы while и until: контроль входа в систему

5.3 Циклы while и until: контроль входа в систему

В гл. 3 цикл for использовался для нескольких итеративных программ. Обычно цикл for охватывает множество имен файлов, как в 'for i in * .с', или все аргументы командного файла, как в 'for i in $*'. Но циклы в языке shell могут быть более общими, чем в этих идиомах, например цикл for в команде which.

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

Ниже приведены основные формы каждого цикла:

for i in список слов
do
 тело цикла, $i последовательно получает значения элементов
done
for i (явно перечисляются аргументы командного файла, т.е. $*)
do
 тело цикла, $i последовательно получает значения аргументов
done
while команда
do
 тело цикла выполняется, пока команда возвращает истина
done
until команда
do
 тело цикла выполняется, пока команда возвращает ложь
done

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

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

while sleep 60
do
 who | grep mary
done

Команда sleep, устанавливающая паузу на 60 с, всегда выполняется нормально (если ее не прервали) и, значит, всегда возвращает код "успех", поэтому в цикле раз в минуту будет проверяться, находится ли Мэри в системе. Недостаток такого решения состоит в том, что если Мэри уже вошла в систему, то нужно ждать 60 с, чтобы узнать об этом. О продолжении же работы Мэри в системе каждую минуту будет поступать сообщение. Цикл можно перевернуть и записать с помощью until, чтобы получать информацию сразу без задержки, если Мэри в данный момент работает в системе:

until who | grep mary do
 sleep 60
done

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

В заключение мы можем оформить команду, дав ей имя и установив в системе:

$ cat watchfor
# watchfor: watch for someone to log in
PATH=/bin:/usr/bin case $# in
0) echo 'Usage: watchfor person' 1>&2; exit 1
esac
until who | egrep "$1"
do
 sleep 60
done
$ cx watchfor
$ watchfor you
you tty0 Oct 1 08:01      
Работает

$ mv watchfor /usr/you/bin Установим в системе

$

Мы заменили grep на egrep, чтобы было можно задавать

$ watchfor 'joe | mary'

и следить за несколькими пользователями.

Более сложный пример: можно контролировать вход в систему и выход из нее всех пользователей и сообщать обо всех фактах входа или выхода. Это можно рассматривать как некоторое дополнение к команде who. Основная идея проста: раз в минуту запускать команду who и сравнивать результат ее действия с результатом, полученным минутой ранее, сообщая обо всех различиях. Вывод команды who хранится в файле, и мы можем записывать его в каталог /tmp. Чтобы отличить свои файлы от файлов, принадлежащих другим процессам, в имена файлов вставляется переменная интерпретатора $$ (номер процесса команды интерпретатора), что является обычной практикой. Имя команды упоминается во временных файлах главным образом для администратора системы. Часто команды (включая данную версию watchfor) оставляют после себя файлы в /tmp, и полезно знать, какая команда это сделала. Здесь ":" — встроенная команда, которая

$ cat watchwho
# watchwho: watch who logs in and out
PATH=/bin:/usr/bin
new=/tmp/wwho1.$$
old=/tmp/wwho2.$$
> $old # create an empty file
while : # loop forever
do
 who >$new
 diff $old $new
 mv $new $old
 sleep 60
done | awk '/>/ { $1 = "in: "; print }
            /</ { $1 = "out: "; print }'
$

только обрабатывает свои аргументы и возвращает код "истина". Мы могли бы заменить ее командой true, просто передающей код завершения "истина" (есть также команда false), но команда ':' более эффективна, поскольку не нужно выполнять эту команду, выбирая ее из файловой системы.

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

Поскольку файл old создается пустым, первый вывод команды watchfor содержит весь список пользователей, находящихся в системе в данный момент. Замена команды, которая создает файл old, на who > $old приведет к тому, что watchfor выдаст только изменения, но это уже — дело вкуса.

Другая программа в цикле следит за содержимым вашего почтового ящика: как только оно изменяется, программа выдает сообщение: "You have a mail" ("У вас есть почта"). Такая программа является полезной альтернативой встроенному в интерпретатор механизму, использующему переменную MAIL. Чтобы показать другой стиль программирования, мы реализовали ее с помощью переменных интерпретатора, а не файлов:

$ cat checkmail
# checkmail: watch mailbox for growth
PATH=/bin:/usr/bin
MAIL=/usr/spool/mail/`getname` # system dependent
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 "You have mail" }'
$

Мы опять воспользовались awk программой, на этот раз — чтобы добиться вывода сообщения только в тех случаях, когда почтовый ящик пополняется, а не просто изменяется. Иначе вы получите сообщение сразу после исключения письма. (Версия, встроенная в интерпретатор, имеет такой недостаток.)

Обычно интервал времени устанавливается равным 60 с, но если командная строка содержит параметр, например

$ chekmail 30

то интервал задается им. Переменная интерпретатора принимает в качестве значения заданное параметрами время или 60 с, если время не задано, с помощью присваивания

t=${1-60}

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

$ var=hello
$ varx=goodbye
$ echo $var
hello
$ echo ${var}x
hellox
$

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

$ echo ${var?}
hello                   
все в порядке, var определено

$ echo ${junk}
junk: parameter not set
стандартное сообщение

$ echo ${junk?error!}
junk: error!            
строка задана

$

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

В другой конструкции ${var-thing} выбирается $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. Возвращаясь к нашему исходному примеру

t=${1-60}

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

$var Значение var; ничего, если var не определено
${var} То же; полезно, если за именем переменной следует буквенно-цифровая строка
${var-thing} Значение var, если оно определено; в противном случае — thing;$var не изменяется
${var=thing} Значение var, если оно определено; в противном случае — thing. Если var не определено, то $var присваивается thing
${var?строка} Если var определено — $var; в противном случае выводится строка и интерпретатор прекращает работу. При пустой строке выводится: var: parameter not set
${var+thing} thing, если $var определено; в противном случае — ничего

Таблица 5.3: Получение значений переменных в языке

Упражнение 5.9

Обратите внимание на реализацию команд true и false в /usr/bin или /bin. (Как бы вы определили, где они находятся?)

Упражнение 5.10

Измените команду watchfor так, чтобы пользователь мог задавать несколько имен, а не вводить 'joe|mary'.

Упражнение 5.11

Напишите версию команды watchwho, которая использует команду comm вместо awk для сравнения новой и старой информации. Какая версия вам больше нравится?

Упражнение 5.12

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

Упражнение 5.13

В чем состоит различие между пустой командой языка shell: и символом примечания #? Нужны ли они?

Оглавление книги


Генерация: 1.136. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз