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

5.1 Совершенствование команды cal

5.1 Совершенствование команды cal

Типичная задача программирования на языке shell сводится к изменению взаимодействия между пользователем и программой, чтобы сделать это взаимодействие более удобным. В качестве примера рассмотрим команду cal(1):

$ cal
usage: cal [month] year
Пока хорошо

$ cal october
Bad argument           
Уже не так хорошо

$ cal 10 1983
  October 1983
  S  M  Tu W  Th F  S
                    1
  2  3  4  5  6  7  8
  9 10 11 12 13 14 15
 16 17 18 19 20 21 22
 23 24 25 26 27 28 29
 30 31
$

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

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

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

Язык shell имеет оператор case, который успешно применяется в таких ситуациях:

case слово in
шаблон) команды ;;
шаблон) команды ;;
...
esac

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

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

$# Число аргументов
$* Все аргументы, передаваемые интерпретатору
$@ Аналогично $*; см. разд. 5.7
$- Флаги, передаваемые интерпретатору
$? Возвращение значения последней выполненной команды
$$ Номер процесса интерпретатора
$! Номер процесса последней команды, запущенной с помощью &
$НOМЕ Аргумент, принятый по умолчанию для команды cd
$IFS Список символов, разделяющих слова в аргументах
$MAIL Файл, изменение которого приводит к появлению сообщения "you have a mail" ("У вас есть почта")
$PATH Список каталогов, в которых осуществляется поиск команд
$PS1 Строка приглашение, по умолчанию принята '$'
$PS2 Строка приглашение при продолжении командной строки, по умолчанию принята '>'

Таблица 5.1: Встроенные переменные интерпретатора

$ cat cal
# cal: nicer interface to /usr/bin/cal
case $# in
0) set `date`; m=$2; y=$6 ;; # no args: use today
1) m=$l; set `date`; y=$6 ;; #1 arg: use this year
*) m=$1; y=$2 ;;             #2 args: month and year
esac
case $m in
jan*|Jan*) m=1 ;;
feb*|Feb*) m=2 ;;
mar*|Mar*) m=3 ;;
apr*|Apr*) m=4 ;;
may*|May*) m=5 ;;
jun*|Jun*) m=6 ;;
jul*|Jul*) m=7 ;;
aug*|Aug*) m=8 ;;
sep*|Sep*) m=9 ;;
oct*|Oct*) m=10 ;;
nov*|Nov*) m=11 ;;
dec*|Dec*) m=12 ;;
[1-9]|10|11|12) ;; # numeric month
*) y=$m; m="" ;;   # plain year
esac
/usr/bin/cal $m $y # run the real one
$

В первом операторе case проверяется число аргументов $# и выбирается подходящее действие. Последний шаблон в этом операторе задает вариант, выбираемый по умолчанию; если число аргументов не 0 и не 1, будет выполнен последний вариант. (Поскольку шаблоны просматриваются по порядку, вариант по умолчанию должен быть последним.) При наличии двух аргументов m и y принимают значение месяца и года, и наша команда cal должна выполняться как исходная команда.

Первый оператор case включает пару нетривиальных строк, содержащих

set `date`

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

$ date
Sat Oct 1 06:05:18 EDT 1983
$ set `date`
$ echo $1
Sat
$ echo $4
06:05:20
$

Итак, мы имеем дело с встроенной командой интерпретатора, возможности которой многообразны. При отсутствии аргументов она выдает, как указывалось в гл. 3, значения переменных окружения. В случае обычных аргументов переопределяются значения $1, $2 и т.д. Поэтому set `date` присваивает $1 — день недели, $2 — название месяца и т.д. Таким образом, при отсутствии аргументов в первом case месяц и год устанавливаются из текущей даты. Если был задан один аргумент, он используется в качестве месяца, а год берется из текущей даты.

Команда set имеет также несколько флагов, из которых наиболее часто используются флаги -v и — для отключения эха команд при обработке их интерпретатором. Такое отключение может оказаться необходимым в процессе отладки сложных программ на языке shell.

Теперь осталось только перевести значение месяца, если оно представлено в строковом виде, в число. Это делается с помощью второго оператора case, который практически очевиден. Единственный нюанс состоит в том, что символ | в шаблонах оператора case, как и в команде egrep, означает альтернативу: малый|большой соответствует варианту "малый" или "большой". Конечно, эти варианты можно было бы задать с помощью [jJ]an* и т.д. Программа допускает задание месяца строчными буквами, поскольку большинство команд работает с входным потоком, где данные записаны строчными буквами (иногда первая буква — прописная), поскольку так выглядит вывод команды date. Правила сопоставления шаблонов приведены в табл. 5.2.

* Задает любую строку, включая пустую
? Задает любой одиночный символ
[ccc] Задает любой из символов в ccc [a-d0-3] эквивалентно [abcd0123]
"..." Задает в точности ...; кавычки защищают от специальных символов. Аналогично действует '...'
c Задает с буквально
a|b Только для выражений выбора; задает а или b
/ Для имен файлов; соответствует только символу / в выражении; для выражений выбора сопоставляется, как любой другой символ
. Если это первый символ в имени файла, то сопоставляется только с явно заданной точкой в выражении

Таблица 5.2: Правила сопоставления шаблонов в интерпретаторе

Два последних варианта второго оператора case относятся к случаю, когда единственный аргумент может быть годом; напомним, что в первом операторе case предполагалось, что аргументом является месяц. Если это число, которым может быть задан месяц, то ничего не происходит (иначе предполагается, что задан год).

Наконец, в последней строке вызывается /usr/bin/cal (настоящая команда cal) с преобразованными аргументами. Наша версия команды cal работает так, как этого мог бы ожидать начинающий:

$ date
Sat Oct 1 06:09:55 EDT 1983
$ cal
October 1983
  S  М Tu  W Th  F  S
                    1
  2  3  4  5  6  7  8
  9 10 11 12 13 14 15
 16 17 18 19 20 21 22
 23 24 25 26 27 28 29
 30 31
$ cal dec
December 1983
  S  M Tu  W Th  F  S
              1  2  3
  4  5  6  7  8  9 10
 11 12 13 14 15 16 17
 18 19 20 21 22 23 24
 25 26 27 28 29 30 31
$

При обращении к cal 1984 будет напечатан календарь на весь 1984 год. Наша обобщенная команда cal выполняет то же задание, что и исходная, но более простым и легко запоминающимся способом. Поэтому мы предпочитаем называть ее cal, а не calendar (что уже является командой), или как-нибудь еще с менее простой мнемоникой, например ncal. При использовании одного и того же имени пользователю не придется вырабатывать новые рефлексы для печати календаря.

Прежде чем завершить обсуждение оператора case, следует объяснить, почему правила сопоставления шаблонов в интерпретаторе отличаются от правил для редактора ed и его производных. Действительно, наличие двух видов шаблонов означает, что нужно изучать два набора правил и иметь два программных фрагмента для их обработки. Некоторые различия вызваны просто неудачным выбором, который никогда не был зафиксирован. В частности, нет никаких причин (кроме того, что так сложилось исторически), по которым ed использует '.' а интерпретатор — '?' для задания единственного символа. Но иногда шаблоны применяются по-разному. Регулярные выражения в редакторе используются для поиска последовательности символов, которая может встретиться в любом месте строки; специальные символы и $ нужны, чтобы направить поиск с начала или конца строки. Но для имен файлов мы хотим, чтобы направление поиска определялось по умолчанию, поскольку это наиболее общий случай. Было бы неудобным задавать нечто вроде

$ ls ^?*.с$ Так не получится

вместо

$ ls *.с

Упражнение 5.1

Если пользователи предпочтут вашу версию команды cal, как бы вы сделали ее общедоступной? Что следует предпринять, чтобы поместить ее в /usr/bin?

Упражнение 5.2

Имеет ли смысл сделать так, чтобы при обращении cal 83 был напечатан календарь за 1983 г.? Как в этом случае задать вывод календаря?

Упражнение 5.3

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

$ cal oct nov

и даже диапазон месяцев:

$ cal oct-dec

Если сейчас декабрь, а вы выполняете обращение cal jan, то какой должен быть напечатан календарь: на январь этого года или следующего? Когда следует прекратить расширять возможности команды cal?

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


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