Приемы профессиональной работы в UNIX

       

ПОЯСНЕНИЯ


Первая строка содержит только знак двоеточия (:) - "нулевую команду". Это связано с тем, что все командные файлы, описываемые в этой книге, сделаны так, чтобы их можно было запускать в среде интерпретатора Bourne shell. Наш комментарий в строке 2, идентифицирующий версию, начинается со знака решетки (#). Си-shell ищет этот знак как первый знак командного файла. Если он найден, то предпринимается попытка выполнить данный командный файл. В противном случае Си-shell передает командный файл интерпретатору Bourne shell. Вот почему мы не хотим начинать первую строку со знака #. Мы, конечно, могли бы оставить первую строку чистой, но чистая строка невидима и может быть случайно удалена. Соответственно мы будем использовать чистые строки в других случаях, чтобы выделить важные участки программы.

Строка 2 идентифицирует версию. Символьная строка @(#) есть специальная последовательность в строке комментария, которая распознается как строка "what" ("что"). Команда what в системе UNIX читает файл и печатает сообщение, которое следует за строкой "what". Чтобы идентифицировать версию данного командного файла, наберите

# what tree и будет напечатано следующее сообщение: tree: tree v1.0 Visual display of a file tree Author: Russ Sage

Строки 4-7 проверяют, не слишком ли много аргументов было передано командной строке. Это осуществляется путем исследования переменной $#, которая представляет собой счетчик числа позиционных параметров командной строки. Если насчитывается более одного параметра, печатается соответствующее сообщение об ошибке в стандартный файл ошибок (stderr) и программа останавливается с плохим значением статуса.

Отметим, что команда echo обычно печатает в стандартный выход (stdout). Мы можем перенаправить stdout в другой файловый дескриптор, указав его. В данном случае мы собираемся печатать в stderr. Синтаксис переводится так: "вывести эту строку и перенаправить ее в файловый дескриптор (&) стандартного файла ошибок (2)". Печать сообщений об ошибках в stderr обеспечивает согласованное поведение командного файла независимо от среды, в которой он запущен.


Строки 4-8 выполняют проверку ошибок. Так как команда thead не имеет никаких опций, любые позиционные параметры, которые начинаются с дефиса (-) являются неверными. Если первым символом первого позиционного параметра оказывается "-", то печатается сообщение "argument error" (ошибка аргумента) вместе с сообщением о способе запуска и команда thead прекращает работу.

Некоторые приемы программирования для интерпретатора shell, используемые в этих строках, довольно часто встречаются в данной книге, поэтому имеет смысл остановиться на них подробнее. Проанализируем строку 4, работающую изнутри наружу. Команда echo выдает содержимое $1 (текущий параметр командной строки), которое передается по программному каналу команде cut. Команда cut используется для того, чтобы выделить определенные символы или группы символов из строки. В данном случае опция -c1 используется для получения только первого символа.




В строке 4 переменная OPT, в которой хранятся необязательные команды оператора find, инициализируется в нулевое значение.

Строки 6-18 выполняют проверку на наличие ошибок. Проверяется, является ли первым символом каждого позиционного параметра символ "-". Если проверка успешна, то проверяется на корректность сам аргумент. Возможными опциями являются -c и -h. Если указано что-либо другое, выводится сообщение об ошибке и программа завершается. Обратите внимание, что с помощью команды shift допустимые опции удаляются из командной строки. Это сделано для того, чтобы впоследствии выражение $@ могло быть использовано для получения только аргументов строки поиска и маршрута. Выражение $@ является другой формой выражения $ *, в которой оно распространяется на все позиционные параметры. Однако в последующем тексте мы увидим одно большое отличие между ними.

Еще один трюк сделан при присвоении значения переменной OPT в строке 10. Этой переменной нам необходимо присвоить значение, которое включает в себя пару кавычек, поскольку кавычки должны быть частью строки поиска, которая в конце концов используется оператором find. Однако обнаружение второй кавычки обычно ЗАВЕРШАЕТ оператор присваивания, а мы этого в данном случае не хотим. Выходом из ситуации является использование символа \, который отменяет специальное значение следующего за ним символа. В данном случае специальное значение было бы концом строки присваивания.



Таким образом, кавычка перед звездочкой (*) в строке 10 рассматривается как часть значения переменной OPT, вместо того, чтобы завершать операцию присваивания. Следующая встреченная кавычка также должна быть сохранена в значении переменной, чтобы указать конец хранимой здесь строки поиска, поэтому она также экранирована символом \. Последняя кавычка в этой строке не экранирована обратной косой чертой. Эта кавычка соответствует своей обычной функции в смысле интерпретатора shell и завершает оператор присваивания.

Вам нужно поэкспериментировать с использованием кавычек, чтобы понять его. Когда какой-то командный файл не желает работать правильно, но ошибок не видно, проверьте правильность использования кавычек. Оставшаяся часть командного файла - это оператор case (строки 21-37), который имеет дело с числом аргументов командной строки. Если нет никаких аргументов, печатается сообщение об ошибке и мы выходим из программы. Мы ведь не можем делать никакого поиска командой grep, если мы даже не знаем, какую строку нужно искать.




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

В строках 6-19 все аргументы командной строки проверяются на корректность. Критерием того, что аргумент есть опция, является дефис в роли первого символа. Заметим, что здесь не разрешено использование синтаксиса "-xyz". Это заставляет вас пользоваться синтаксисом "-x -y -z". Хотя этот момент может показаться несущественным, на самом деле он важен. Всегда нужно достигать компромисса между быстрой разработкой командного файла при согласии на недостатки жесткого синтаксиса - и разрешением гибкого формата за счет дополнительных усилий по кодированию и отладке и за счет более медленного выполнения. Ваш выбор зависит от ваших приоритетов, от количества людей, использующих ваше инструментальное средство, и от того, насколько критична скорость выполнения. Конечно, если скорость критична, вы, вероятно, захотите использовать каким-то образом язык Си. Мы оставляем обработку конкатенированных опций в качестве упражнения для читателя.

Цикл for проходит по всем позиционным параметрам. Если первым символом аргумента является "-", то он сверяется со списком допустимых аргументов с помощью оператора case в строках 9-17. Опция "-l" изменяет переменную формата, после чего убирается из рассмотрения. Это делается для освобождения этой позиции, чтобы конечным результатом были просто имена файлов в командной строке.

Опция "-s" также изменяет переменную формата. Однако, вместо того, чтобы убрать опцию из командной строки, она ликвидирует всю командную строку и заменяет ее символом "l". Это заставляет цикл for проходить только одну итерацию, так как в командной строке теперь только один параметр. Благодаря такому обращению с командной строкой, нам не нужен другой цикл: мы можем использовать тот же цикл, что и в определении маршрута, без всяких модификаций. Поскольку после опции s не ожидается никаких имен файлов, мы больше не хотим рассматривать командную строку.




В строках 4-8 проверяется, является ли первым аргументом командной строки -m - опция команды more. Если эта опция найдена, то в переменную MORE заносится указание конвейера и команда more. Тем самым устанавливается постобработка, которую следует применить к выходу команды ls. Затем эта опция убирается из командной строки. Это делается для того, чтобы остаток командной строки можно было передать команде ls, не вызвав при этом нежелательных эффектов. Если первой опцией не является -m, переменной MORE присваивается нулевое значение, чтобы она впоследствии не влияла на командную строку.

Строка 10 - это командная строка, которую вы бы использовали на старой UNIX-машине типа Version 7 или System III. Она не имеет ни встроенной опции для печати символов косой черты (/) и звездочек (*), ни возможности печати в виде колонок. Вы должны пожертвовать первой возможностью, а распечатки в виде нескольких колонок можно получить с помощью команды pr системы UNIX. Команда pr использована с опцией "-5t", поэтому она печатает в пять колонок (что обычно приемлемо, но если встречаются длинные имена файлов, то пяти колонок может оказаться слишком много) и не печатает верхний и нижний колонтитулы. Благодаря отказу от колонтитулов, 24-строчный формат не слишком неудобен для вас.

Отметим, что здесь использована команда eval. Это специальная встроенная команда интерпретатора shell, которая выполняет перевычисление текущей строки, подлежащей выполнению. Интерпретатор shell повторно анализирует эту строку, чтобы раскрыть значение имен переменных в командной строке и обеспечить распознавание переменных как таковых. Здесь мы перевычисляем переменную MORE. Напомним, что мы поместили в эту переменную конвейер. Если мы не перевычислим командную строку, то команда pr попытается открыть файлы "|" и "more", которые не существуют. Для того, чтобы shell вместо этого воспринял эти символы как указания конвейеров и программ, и используется команда eval.

Строка 10 имеет еще одну особенность. В командной строке уже есть один конвейер. Откуда shell знает, трактовать ли символ "|" как имя файла или как конвейер? Благодаря тому, что аргумент команды eval заключен в кавычки. Это указывает команде eval сохранить все, что находится в кавычках, без изменений, но раскрыть значение переменной MORE и поместить его в конец командной строки, находящейся в кавычках. Несколько непонятно, но если вы думаете об этом пару лет, оно становится осмысленным.




Если первым позиционным параметром является -m, то в строке 4 инициализируется переменная MORE для подключения конвейера и программы /usr/bin/more. (Вопрос о том, почему используется абсолютное маршрутное имя, обсуждался в предыдущем разделе.) Затем символьная строка -m командой shift убирается из командной строки. Если же первой опцией не является -m, то переменная MORE устанавливается в нуль, чтобы не влиять на повторный разбор командной строки, выполняемый с помощью команды eval (строка 10).

В строке 10 команда eval использована для получения результирующей командной строки. Команда ls вызывается с опциями -al (выдача списка всех файлов в длинном формате), которые мы установили по умолчанию. Затем берутся аргументы командной строки (минус первый аргумент, если это был -m, который мы убрали командой shift). Этими аргументами могут быть дополнительные опции команды ls плюс имена файлов или каталогов. В конце строки значение переменной MORE обеспечивает конвейер с командой more, если была указана опция -m. В противном случае значение переменной MORE равно нулю и не оказывает никакого влияния на анализ содержимого командной строки.

Что произошло бы, если бы пользователь указал опцию -m в качестве второй (или последующей) опции? В этом случае опция -m передалась бы команде ls. Команда ls трактовала бы эту опцию как "потоковый вывод", а это совсем не то, что мы хотели. Однако команда ls была вызвана также с опцией -l, которая отменяет опцию -m в соответствии с текстом программы ls. Вы не получили бы вывод с помощью команды more, но ваши выходные данные по-прежнему были бы выведены в правильном формате.




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

В строке 4 проверяется, включены ли какие-либо параметры. Если параметры есть, то они обрабатываются. Если не используются никакие параметры, ничего не делается и управление передается на строку 27. Если были использованы какие-либо аргументы и первым символом является знак минуса (-), то выполняется оператор case для определения того, какой тип файла указан. Переменная KIND устанавливается в соответствии с типом файла, и данный параметр удаляется из командной строки командой shift. Если аргумент не совпадает ни с одной из допустимых опций, то ему соответствует случай *, что означает ошибку. На стандартное устройство регистрации ошибок выводится соответствующее сообщение об ошибке и синтаксическая подсказка, после этого kind завершается с плохим статусом выполнения.

В строке 27 производится проверка того, установлена переменная KIND или равна нулю. Если она равна нулю, в нее подставляется символьная строка "text". Если KIND уже установлена, то она не меняется. Это неплохой оператор присвоения значения по умолчанию. Таким образом, пользователь не обязан указывать опцию -t в командной строке. Если же опция -t была указана, то ей есть что сопоставить в операторе case.

Оставшаяся часть программы в строках 29-35 представляет собой еще один оператор case, который проверяет количество аргументов, оставшихся в командной строке после обработки ошибок. Если была указана какая-либо опция, то переменная KIND установлена и опция убрана командой shift. В командной строке могли остаться только аргументы, которые являются именами файлов или маршрутами. Если к тому времени, когда мы уже готовы к заключительной обработке, не осталось никаких аргументов, то значит в командной строке не было указано ни одного имени файла.

В этом случае в строках 30-33 организовывается цикл, который читает имена файлов из стандартного ввода, запускает команду file и использует команду fgrep для определения того, соответствует ли тип файла, выданный командой file, интересующему нас типу (хранимому в переменной KIND). Затем мы используем команду cut для выделения того, что нам нужно. Обычный вывод команды file содержит имя файла, двоеточие и затем описание. Нам нужно только имя файла, поэтому мы вырезаем первое поле, используя разделитель ":". Когда никакие данные больше не поступают, цикл while завершается, мы попадаем в конец оператора case и выходим из программы.

Если же аргументы НАЙДЕНЫ в командной строке, то вместо всего этого выполняется ветвь оператора case в строке 34. С помощью обозначения $@, имена всех файлов в командной строке включены в команду file. Таким образом, не нужен никакой цикл. Во всем остальном обработка идентична строке 32.




Поскольку в этом командном файле не так много текста, то все довольно понятно, нет ни обработки ошибок, ни других дополнений. Просто нехитрый вызов команды more. Полное имя здесь указано с целью повышения быстродействия, как мы обсуждали ранее. Вы должны перепроверить местонахождение вашей команды more. В системе Berkeley она может находиться в каталоге /usr/ ucb/more. Воспользуйтесь командой path more для определения этого места и вставьте соответствующий маршрут вместо указанного нами.

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

:.!path more

Здесь происходит переход в shell и запуск команды path (:!), затем выход команды path (который представляет собой полное маршрутное имя) помещается в буфер редактора в самом начале текущей строки (.). После этого вы имеете эти данные в вашем редактируемом файле и при необходимости можете отредактировать их.




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

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

Затем мы обрабатываем каждый аргумент командной строки в цикле (строки 11-17). Все аргументы должны быть именами файлов, поэтому каждый из них проверяется на то, существует ли он как обычный файл. Если это не файл, то в стандартный файл ошибок выводится сообщение об ошибке. Тем не менее программа не завершается. Не следует аварийно прекращать всю программу только потому, что нет указанного файла. Мы пропускаем его и идем до конца списка аргументов. Это особенно актуально, если данная команда используется как фоновая во время выполнения другой работы. Пользователь скорее согласится с тем, чтобы было выполнено побольше работы, чем не сделано вообще ничего. Это решение, принятое в данной программе, и вы можете изменить его, если оно не подходит в вашей ситуации.

Если имени соответствует допустимый файл, оно добавляется в список хороших имен файлов. Этот список становится главным списком для команды nroff.

После того как все аргументы проверены, мы в строке 9 строим и выполняем командную строку nroff.

Опция -rO0 для nroff указывает макросам обработки рукописей (пакету mm) установить регистр, который имеет дело с отступом текста, в состояние, соответствующее отступу в 0 символов. Это значит, что весь текст начинается с крайней левой позиции, т.е. выровнен слева. Путем проведения экспериментов я обнаружил, что левое выравнивание текста программой nroff и установка отступа для принтера дает наиболее надежный вывод на печать. В противном случае, если вы установите отступ текста в nroff и отступ в принтере, то может произойти настоящее столкновение, когда дело коснется вывода колонок в странице. Вы можете изменить это место, если ваши программы вывода или устройства печати ведут себя как-то иначе. Опция -mm указывает программе nroff просмотреть библиотеку макросов обработки рукописей, чтобы определить, используются ли какие-либо из них во входном документе. Эти макросы очень большие и требуют много времени центрального процессора. Если вам необходимо использовать их, то вам потребуется большой компьютер или компьютер, специально предназначенный для этой цели, чтобы добиться хорошего времени получения результата.

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




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

В строке 12 инициализируется переменная NAME. Это действие выполняется по умолчанию, поэтому данная строка дает возможность не указывать опцию в командной строке. Оператор if в строке 13 означает: "Если первым символом первого аргумента является дефис", то нужно проверить, какая это опция.

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

Если установлена опция -d, то переменная NAME инициализируется для поиска символьной строки, соответствующей именам файлов программной разработки. Обратите внимание, что двойные кавычки внутри оператора экранированы, т.е. впереди них стоят символы наклонной черты. Командный интерпретатор shell воспринимает кавычки вокруг строки поиска на первой фазе синтаксического разбора без отмены присвоения, тем самым оставляя двойные кавычки последующей команде find.

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

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

Строки 29-43 - это главный цикл всей программы. Оператор find должен быть повторно проанализирован командой eval, поскольку переменная NAME содержит нужные нам данные. Если бы не было команды eval, то подстановка символьных строк выполнялась бы неправильно. Обратите внимание, что переменной NAME не требуются кавычки в строке 24. Они уже есть в переменной NAME, так как были обработаны командой eval.

Оператор find находит только файлы типа f, или обычные файлы, т.е. не каталоги и не символьные или блочные устройства. Здесь под "обычными файлами" понимается, что они еще могут быть файлами данных или исполняемыми. Если значение переменной NAME равно нулю, оно не влияет на командную строку. Если NAME содержит символы файлов программной разработки, то они становятся частью команды find при выполнении команды eval. Это накладывает ограничения на шаблон поиска команды find. Когда файлы найдены, их имена выводятся в стандартный вывод.



Содержание раздела