также, что коды статуса выхода в интерпретаторе shell противоположны тем, которые используются при программировании на языке Си. В Си истинное значение есть 1, ложное отлично от 1. При программи- ровании на языке shell успешным статусом выхода (истиной) является 0, а плохим статусом (ложью) ненулевое значение. Вы, возможно, удивитесь, почему мы так беспокоимся о том, чтобы вернуть ложный статус выхода, если командный файл собирается просто напечатать сообщение об ошибке и прекратить работу. Дело в том, что все инструментальные средства системы UNIX должны быть спроектированы так, чтобы они могли быть связаны с другими командами и процессами, в которые они могут быть встроены. Возможно, что другой команде необхо- димо будет вызвать команду tree и проверить, корректно ли она отрабо- тала. Хорошим стилем проектирования программных средств является прог- нозирование и разрешение многих способов использования программы. Строки 9-15 проверяют, чтобы любые параметры, передаваемые ко- мандной строке, были действительно каталогами, как указано в нашем синтаксисе. Напомним, что в командной строке может быть помещен только один каталог. Если мы используем только один параметр и этот параметр не является каталогом, то мы печатаем сообщение об ошибке и выходим. Таким образом, операторы проверки гарантируют, что либо не использу- ется ни один параметр, либо единственный используемый параметр явля- ется корректным каталогом. Мы подошли к сердцу команды tree - это строки 17-20. Главным здесь является команда find системы UNIX. Каталог, в котором ведется поиск, определяется при запуске команды. Синтаксис ${1:-.} является формой параметрической подстановки и означает следующее: если $1 (что является первым позиционным параметром) установлен (иными словами, если аргумент был передан командной строке и был ненулевым), то нужно использовать это значение. В противном случае следует использовать ка- талог . (текущий каталог). Этот тип подстановки дает нам возможность запускать команду tree без указания имени каталога (когда после tree на командной строке ничего не следует),- то есть работать в режиме "по умолчанию", что часто используется в различных файловых инструментах. Команда find выводит на печать полное имя каждого файла, который ей встречается. Поскольку не используется никакая специальная опция для селекции файлов, печатаются все имена. После этого все полные име- на файлов сортируются для более удобного чтения. Такая сортировка несколько увеличивает время работы команды, однако наглядность резуль- тата говорит о том, что это время было потрачено с пользой. Далее, отсортированные полные имена файлов передаются по прог- раммному каналу команде sed системы Unix. Sed - это "потоковый редак- тор", очень гибкое средство, которое может быть использовано для иден- тификации и обработки различных образцов текста. Опции -e являются операциями редактирования, применяемыми к поступающим данным. Первый оператор просто сообщает команде sed, что нужно напечатать первую строку, затем удалить строку 1. Это делается для того, чтобы напеча- тать название корневого каталога, который исследуется. Этой строке не требуется никакой дальнейшей модификации, так как корневой каталог не имеет никаких дополнительных элементов путей доступа, которые нужно было бы трансформировать в символы косой черты для показа отступов. Удаление первой строки связано с тем, что она не нужна в дальнейшей работе. Вторая операция редактирования является командой подстановки. Она заменяет каждый символ, отличный от символа косой черты (вплоть до первого символа /) на последовательность пробелов и затем один символ (/). Это избавляет нас от печатания имен промежуточных каталогов впе- реди полного имени файла. Буква g в конце этой строки означает, что эта операция выполняется глобально, то есть для всех считываемых сим- волов. Итак, теперь строка состоит из начального элемента пути и одной или более последовательностей пробелов, разделенных символами косой черты. Символы обратной косой черты (\) в конце операций редактирова- ния - это символы продолжения, которые сообщают команде sed, что нужно продолжить работу со следующей строкой в текущем пакете операций ре- дактирования. Третья операция редактирования (строка 19) также является коман- дой подстановки и заменяет каждый символ, который не является пробелом (вплоть до символа /) на "не символ" и один символ косой черты. Этот оператор удаляет пробелы из предыдущего результата редактирования и смещает символ в самую левую позицию. Это создает гнездовую индикацию, которую мы видели в предыдущем примере. Последняя операция редактирования (в строке 20) заменяет символ косой черты и все отличные от него символы (до конца строки) просто на символы, отличные от /. Отметим, что это устраняет самый правый символ /, который присутствует в листинге команды find. В результате остается имя подчиненного файла, сдвинутое вправо. Отметим синтаксис \1 команды sed - признак, относящийся к первому (в данном случае единственному) регулярному выражению в скобках, кото- рое ему предшествует. В данном случае команде sed указано пройти сим- волы, соответствующие регулярному выражению - символы, отличные от /. 2.1.2. thead - печать начала каждого файла ----------------------------------------------------- ИМЯ: thead ----------------------------------------------------- thеаd Печатает заголовок (первые несколько строк) файлов. НАЗНАЧЕНИЕ Пройти файловое дерево и напечатать первые несколько строк каждо- го файла. Если не указан каталог, то thead действует как фильтр. ФОРМАТ ВЫЗОВА thead [dir...] ПРИМЕР ВЫЗОВА $ find $HOME/src -name "*.c" -print | sort | thead Печатает заголовки (первые несколько строк) всех моих исходных файлов на языке Си. ТЕКСТ ПРОГРАММЫ 1 : 2 # @(#) thead v1.0 Prints head of files in tree Author: Russ Sage 2а Печатает заголовки файлов в дереве 4 if [ "`echo $1|cut -c1`" = "-" ] 5 then echo "$0: arg error" 6 echo "usage: $0 [dir ...]" 7 exit 1 8 fi 10 case $# in 11 0) while read FILE 12 do 13 if file $FILE | fgrep text >/dev/null 2>&1 14 then echo "\n:::::::::::::::::::::" 15 echo " $FILE" 16 echo "\n:::::::::::::::::::::" 17 head -15 $FILE 18 fi 19 done;; 20 *) for NAME in $* 21 do 22 find $NAME -type f -print | sort | wile read FILE 23 do 24 if file $FILE | fgrep text >/dev/null 2>&1 25 then echo "\n:::::::::::::::::::::" 26 echo " $FILE" 27 echo "\n:::::::::::::::::::::" 28 head -15 $FILE 29 fi 30 done 31 done;; 32 esac ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ FILE Содержит имя каждого файла NAME Имя каталога, заданное в командной строке ОПИСАНИЕ ЗАЧЕМ НУЖЕН КОМАНДНЫЙ ФАЙЛ thead? Как уже объяснялось ранее в этой главе, иерархическая файловая система является очень значительной частью системы UNIX. Однако, толь- ко несколько команд в UNIX имеют дело непосредственно с рекурсивным поиском файлов. Единственный способ расширить возможности системы - создать новые рекурсивные утилиты, работающие с файлами. В данном слу- чае мы соединим нашу стратегию поиска по дереву с командой head систе- мы UNIX для упрощения идентификации содержимого всех файлов в выделен- ном сегменте файлового дерева. Иногда у нас, возможно, будет возникать желание просмотреть файлы в более чем одном каталоге. В больших проектах разработки программного обеспечения файлы обычно создаются в нескольких иерархических катало- гах. Thead может работать со множеством путей доступа и выводить заго- ловки (несколько первых строк файлов) в виде непрерывного потока. ЧТО ДЕЛАЕТ thead? Thead - это препроцессорная команда к команде head системы UNIX. Команда head очень примитивна, но, добавляя к ней управляющую структу- ру и логику, мы можем создать очень полезное инструментальное средство, которого нет в стандартной среде UNIX. Например, мы захотели просмотреть заголовки всех текстовых файлов в нашем регистрационном каталоге. Если у нас имеется большое число подкаталогов, нам необходимо все их пройти и просмотреть все файлы, содержащиеся в них. Мы можем сделать это с помощью команды $ thead $HOME Если мы хотим просмотреть только исходные файлы на языке Си (*.c), то представленный выше синтаксис не годится для этого. Он не обладает достаточной гибкостью. Нам необходимо иметь способ указать (или квалифицировать) файлы в $HOME перед тем, как просматривать их. Так как команда thead может воспринимать полные имена файлов, мы можем использовать следующую команду: $ find $HOME -name "*.c" -print | sort | thead Команда find генерирует список файлов с расширением C, который сортируется и подается по каналу на вход команде thead. Как видно из представленных двух примеров, весьма полезной для командного файла является возможность получать входные данные либо из аргументов командной строки (как в первом примере), либо по программ- ному каналу (как во втором примере). Способность использовать прог- раммный канал позволяет вам применять какие-либо другие команды систе- мы UNIX, которые могут отбирать входные данные для вашего командного файла. Команда с такой двойной возможностью называется ФИЛЬТРОМ. Среди стандартных команд системы UNIX вы найдете лишь несколько команд-филь- тров, таких как wc, awk, sort. Эти два способа поступления входных данных в программы делают ин- терфейс с командными файлами очень гибким. Мы можем подстраивать прог- рамные средства под наши нужды, а не подстраивать наши желания под имеющееся программное обеспечение. Аргументами для команды thead являются каталоги. Никаких опций, начинающихся со знака "-" нет, только каталог или полные имена файлов. Команда thead знает из синтаксиса, какой способ запуска команды будет использоваться. Если командная строка содержит имя файла, thead просмотрит все позиционные параметры. Если никакие имена не указаны, thead читает стандартный ввод (stdin) и останавливается, когда встре- чает EOF. (Такое бывает в случае, когда команда thead получает входные из программного канала.) Для каждого файла, с которым работает thead, выполняется контроль - текстовый ли это файл. Применение команды head к исполняемым модулям приводит к выводу "таинственных" символов на экран и иногда может выз- вать дамп оперативной памяти. ПРИМЕРЫ 1. $ thead /etc Печатает данные из каждого текстового файла, находящегося в ката- логе /etc. Очень полезная команда, так как большинство файлов в /etc являются исполняемыми модулями. Удобно иметь возможность быстро изоли- ровать текстовые файлы. 2. $ thead /usr/include Просматривает все подключаемые файлы (*.h), даже в системном под- каталоге sys. 3. $ find $HOME -ctime 0 -print | thead Ищет все файлы в вашем регистрационном каталоге, которые были из- менены в течении последних 24 часов. Для каждого файла проверяется, текстовый ли он. Если файл текстовый, то он печатается. ПОЯСНЕНИЯ Строки 4-8 выполняют проверку ошибок. Так как команда thead не имеет никаких опций, любые позиционные параметры, которые начинаются с дефиса (-) являются неверными. Если первым символом первого позицион- ного параметра оказывается "-", то печатается сообщение "argument error" (ошибка аргумента) вместе с сообщением о способе запуска и ко- манда thead прекращает работу. Некоторые приемы программирования для интерпретатора shell, используемые в этих строках, довольно часто встречаются в данной кни- ге, поэтому имеет смысл остановиться на них подробнее. Проанализируем строку 4, работающую изнутри наружу. Команда echo выдает содержимое $1 (текущий параметр командной строки), которое пе- редается по программному каналу команде cut. Команда cut используется для того, чтобы выделить определенные символы или группы символов из строки. В данном случае опция -c1 используется для получения только первого символа. КОМАНДА cut ДЛЯ BSD В системе BSD нет команды cut, но следующий командный файл все же "вырезает" первое непустое поле в текущем аргументе. Предположим, мы используем команду для генерации целого набора строк. В данном случае это команда who: for NAME in 'who | sed "s/^\([^ ]*\).*/\1/"' do done Для каждой обнаруженной строки (аргумента) команда sed должна подставить вторую строку символов вместо первой строки. Первая строка - это строка, которая вырезается. Мы ищем от начала строки (^) символ, отличный от пробела ([^ ]), за которым следует любое число непустых символов (*). Эта операция прерывается по достижении пробела. Набор непустых символов ограничивается обратными косыми чертами \( и \). Впоследствии ссылка на этот набор дается в виде \1. Символы .* означа- ют, что после того, как найден пробел, необходимо считать подходящими все символы до конца строки. Мы находимся фактически сразу после того, что заключено в пару символов \( и \). Группируя первый набор симво- лов, отличных от пробела, мы получаем то, что является результатом ра- боты команды "cut -f1". В этом месте мы подходим к знакам ударения (`), окаймляющим все выражение. Они берут результат работы всех команд, заключенных в знаки ударения и передают на следующую охватывающую структуру в наших вло- женных выражениях. Этот следующий уровень окаймления указан кавычками. Кавычки превращают символ в строку, чтобы его можно было сравнить с символом "-". Следующий слой - квадратные скобки, указывающие условие для оператора if. Это приводит к тому, что генерируется нулевое (исти- на) или ненулевое (ложь) условие, которое управляет тем, будет ли вы- полнена часть then оператора if-then. Мы не собираемся подробно анализировать много строк данного ко- мандного файла, но мы хотим показать вам, как читать выражение или всю строку текста программы так, чтобы это имело смысл. Остальная часть командного файла представляет собой один огромный оператор выбора (case). Аргументом, используемым для ветвления, явля- ется число позиционных параметров в командной строке. Если позиционных параметров нет, то в строках 11-19 активируется цикл while. Заметим, что цикл while выполняет оператор чтения, но не указывает, откуда дол- жен быть взят его вход. Это связано с тем, что входом по умолчанию яв- ляется стандартный ввод (stdin). Для каждого имени файла, которое чи- тается из стандартного ввода, запускается команда file системы UNIX. Выход команды file передается по программному каналу команде fgrep (а не grep, что увеличивает скорость), чтобы посмотреть, является ли файл текстовым. Фактический выход команды fgrep перенаправляется на нулевое уст- ройство (в бесконечную область памяти), поскольку он нам не нужен. Нас интересует лишь код возврата после выполнения всего конвейе- ра. Если команды file и fgrep отработали успешно, кодом возврата явля- ется ноль. Это истинное значение, поэтому выполняется участок цикла после then (строки 14-17). Если файл не существует или не является текстовым, то код возврата ненулевой, и условный оператор завершается. Это приводит нас в конец цикла, выполняется следующая итерация цикла while и мы рассматриваем следующий аргумент из стандартного ввода. Теперь рассмотрим обработку, выполняемую по then (строки 14-17). Для каждого файла, который является текстовым, печатается строка двое- точий (:) до и после имени файла, а команда head системы UNIX печатает первые 15 строк. Такой сценарий продолжается, пока не закончатся дан- ные в стандартном вводе. Рассмотрим другую альтернативу, покрываемую данным оператором вы- бора. Она обрабатывает ситуацию, когда имеется несколько позиционных параметров (что указано символом * в операторе case). Цикл for пробе- гает все параметры (строка 20). Звездочка (*) в операторе case означа- ет, что подходит любое значение, которое не подошло ранее. Это улавли- вающая (catchall) опция. Цикл for использует аргумент $* в качестве своего входа. Он представляет значения всех позиционных параметров, что является фактически всей командной строкой, исключая имя утилиты. Команда find используется для поиска всех нормальных файлов в ка- талоге. "Нормальные" файлы не означает "только текстовые файлы", поэ- тому мы проверим это позже. Выход команды find передается по каналу команде sort, чтобы сделать его более наглядным. Отсортированный список передается по каналу в цикл while, который помещает имя файла в переменную FILE (строка 27). Проверяется, текстовый ли файл, затем он печатается командой head. Если мы сравним строки 13-18 и строки 24-29, то мы увидим, что это один и тот же код. В большинстве языков программирования это озна- чало бы, что мы должны оформить эти строки как процедуру и вызывать ее, когда нужно. Язык программирования интерпретатора shell, хотя и довольно мощный, не имеет хорошего способа реализации процедур. Последний интерпретатор shell в System V имеет функции, которые позво- ляют решить эти проблемы. Отметим, что внутренний цикл while повторяется на каждом файле, который существует в определенном каталоге, а внешний цикл for прохо- дит от каталога к каталогу. ВОЗМОЖНЫЕ МОДИФИКАЦИИ Для увеличения гибкости хорошо бы добавить опции, чтобы вы могли переходить на команду find непосредственно из thead. Полезными аргу- ментами были бы -name для изолирования образцов имен файлов и -ctime для обработки изменений, связанных со временем. Еще одной привлекательной особенностью было бы добавление опции грамматического разбора (основанной на -) и опции -n, указывающей, что из команды head должно быть напечатано n строк. ВОЗМОЖНЫЕ ИССЛЕДОВАНИЯ В чем отличие между двумя следующими операторами? $ find $HOME -name "*.c" -print | thead и $ find $HOME -name "*.c" -exec head {} \; Они выглядят очень похоже, и они действительно похожи. Они обра- батывают одни и те же файлы и печатают одни и те же данные из каждого файла. Основное отличие в том, что строка, которая использует thead, печатает хорошее оформление вокруг имени файла, а чистая команда find печатает непрерывный поток текста так, что очень трудно определить, какой файл вы просматриваете. 2.1.3. tgrep - поиск строк в дереве файловой системы -------------------------------------------------------------- ИМЯ: tgrep -------------------------------------------------------------- tgrep Поиск строки по шаблону в дереве файлов НАЗНАЧЕНИЕ Обходит файловое дерево и ищет в каждом файле указанную строку. Если не указан никакой каталог, tgrep действует как фильтр. ФОРМАТ ВЫЗОВА tgrep [-c|-h] string [file ...] ПРИМЕР ВЫЗОВА # tgrep "profanity" / Поиск слова "profanity" по всей системе (суперпользователь снова на тропе войны!) ТЕКСТ ПРОГРАММЫ 1 : 2 # @(#) tgrep v1.0 Search for string in tree Author: Russ Sage 2а Поиск строки в дереве 4 OPT="" 6 for ARG in $@ 7 do 8 if [ "`echo $ARG|cut -c1`" = "-" ] 9 then case $ARG in 10 -c) OPT="-name \"*.c\"" 11 shift;; 12 -h) OPT="-name \"*.h\"" 13 shift;; 14 *) echo "$O: incorrect argument" >&2 15 echo "usage: $O [-c|-h] string [file ...] >&2 16 exit 1;; 17 esac 18 fi 19 done 21 case $# in 22 0) echo "$O: argument error" >&2 23 echo "usage: $O [-c|-h] string [dir ...]" >&2 24 exit 2 25 ;; 26 1) while read FILE 27 do 28 grep -y "$1" $FILE /dev/nul 29 done 30 ;; 31 *) STRING=$1; shift 32 eval find "$@" -type f $OPT -print | sort | while read FILE 33 do 34 grep -y "$STRING" $FILE /dev/null 35 done 36 ;; 37 esac ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ FILE Содержит имя каждого файла OPT Содержит специальные опции команды find STRING Временная переменная, в которой содержится строка поиска ОПИСАНИЕ ЗАЧЕМ НАМ НУЖЕН КОМАНДНЫЙ ФАЙЛ tgrep? Как мы могли видеть на примере двух предыдущих утилит, рекурсив- ный просмотр файлов очень полезен. Он сохраняет время, поскольку поз- воляет избежать поиска файлов вручную, а также создает средства, кото- рые могут быть использованы в более мощных утилитах. Чем больше име- ется созданных нами средств, тем больше новых средств мы можем постро- ить с их помощью. Единственная проблема заключается в том, что вы должны позаботиться об их взаимозависимости (каким утилитам или средствам требуются другие утилиты или средства и кто на кого влияет). Еще одна область, где UNIX не имеет "родной" рекурсивной команды - это обработка строк. Семейство команд типа grep очень велико, но все они работают только по одному фиксированному маршрутному имени файла. Нам необходим препроцессор для команды grep. Правда, мы можем дать запрос на все файлы во всей системе или какой-либо ее части по нашему выбору. Если мы попытаемся сделать это вручную, то это означает, что мы должны много раз нажимать на клавиши, что может привести к син- таксической ошибке. Вы также должны точно помнить, как вы создавали командную строку, если вы в следующий раз захотите выполнить такую же задачу. Зачем выполнять грязную работу? Для этого существует компь- ютер. Создавая программу автоматического обхода дерева файлов, мы осво- бождаемся для того, чтобы направить нашу энергию на более важные вещи вместо того, чтобы выпутываться из ситуации в случае какого-либо слиш- ком специфичного синтаксиса. Один раз создав достаточно мощные средства доступа к файлам, мы можем посвятить наше время написанию программ, обрабатывающих файлы данных для решения каких-либо задач. ЧТО ДЕЛАЕТ tgrep? Основным предназначением tgrep является обеспечение большей гиб- кости и легкости использования возможностей команды grep. Ее синтаксис точно такой же, как и у grep, за исключением допустимых типов файлов. В команде grep UNIX в качестве аргумента может указываться практически любой файл, но указание текстового файла имеет наибольший смысл. В ко- манде tgrep также могут использоваться текстовые файлы, но наибольший смысл имеет указание каталогов, поскольку мы ищем имена файлов. Коман- да find работала бы не очень хорошо, если бы пыталась извлечь множест- во имен файлов из текстового файла. В командной строке может указы- ваться множество имен каталогов, поскольку все они используются как начальное место поиска оператора find. По умолчанию tgrep находит все обычные файлы. В этой программе нет никакой проверки на то, текстовый файл или нет, поскольку мы не пытаемся напечатать все на экран. Поэтому мы ищем строки символов во всех файлах, начиная от архивных и заканчивая исполняемыми. Если вы хотите выбрать типы файлов, используйте две опции, -c и -h. Опция -c заставляет команду UNIX find искать только файлы с имена- ми вида *.c. Аналогично, опция -h соответствует файлам *.h. Эти опции могут быть полезны для управления программами на языке Си, с которыми мы более детально познакомимся в главе 4. Эти опции не самое важное, но они показывают, как легко добавить новые опции в программу. Если при вызове была указана какая-то иная опция, кроме упомяну- тых, выводится сообщение об ошибке и программа останавливается. Подоб- но thead, tgrep является фильтром. ПРИМЕРЫ 1. $ tgrep unix $HOME Поиск любого вхождения слова unix во всех файлах моего регистра- ционного каталога. 2. $ tgrep -c "^sleep()$" $HOME/src Поиск выражения (начало строки, символьная строка, конец строки) во всех исходных файлах на языке Си в регистрационном каталоге с исходными текстами (опция -c). 3. # find /usr/src -name "*.c" -print | tgrep "ioctl" Поиск всех вызовов ioctl в исходных Си-файлах, начиная с каталога /usr/src. (Обратите внимание, что я являюсь суперпользователем. Это видно из того, что я занимаюсь поиском в ограниченной части системы, а именно в исходных дистрибутивах, а также из того, что в качестве сим- вола приглашения используется символ "#".) 4. $ tgrep "| more" `find . -type f -print` Поиск символа вертикальной черты (|), после которого следует сло- во more, в списке имен файлов, генерируемом оператором find. Find пе- чатает имена всех файлов текущего каталога и всех подкаталогов, кото- рые являются обычными файлами. 5. $ tgrep trap /bin /usr/bin /etc Поиск команды прерывания (trap) во всех командных файлах интерп- ретатора shell, которые имеются в трех каталогах. ПОЯСНЕНИЯ В строке 4 переменная OPT, в которой хранятся необязательные ко- манды оператора find, инициализируется в нулевое значение. Строки 6-18 выполняют проверку на наличие ошибок. Проверяется, является ли первым символом каждого позиционного параметра символ "-". Если проверка успешна, то проверяется на корректность сам аргумент. Возможными опциями являются -c и -h. Если указано что-либо другое, вы- водится сообщение об ошибке и программа завершается. Обратите внима- ние, что с помощью команды shift допустимые опции удаляются из команд- ной строки. Это сделано для того, чтобы впоследствии выражение $@ мог- ло быть использовано для получения только аргументов строки поиска и маршрута. Выражение $@ является другой формой выражения $ *, в которой оно распространяется на все позиционные параметры. Однако в последую- щем тексте мы увидим одно большое отличие между ними. Еще один трюк сделан при присвоении значения переменной OPT в строке 10. Этой переменной нам необходимо присвоить значение, которое включает в себя пару кавычек, поскольку кавычки должны быть частью строки поиска, которая в конце концов используется оператором find. Однако обнаружение второй кавычки обычно ЗАВЕРШАЕТ оператор присваива- ния, а мы этого в данном случае не хотим. Выходом из ситуации является использование символа \, который отменяет специальное значение следую- щего за ним символа. В данном случае специальное значение было бы кон- цом строки присваивания. Таким образом, кавычка перед звездочкой (*) в строке 10 рассмат- ривается как часть значения переменной OPT, вместо того, чтобы завер- шать операцию присваивания. Следующая встреченная кавычка также должна быть сохранена в значении переменной, чтобы указать конец хранимой здесь строки поиска, поэтому она также экранирована символом \. Последняя кавычка в этой строке не экранирована обратной косой чертой. Эта кавычка соответствует своей обычной функции в смысле интерпретато- ра shell и завершает оператор присваивания. Вам нужно поэкспериментировать с использованием кавычек, чтобы понять его. Когда какой-то командный файл не желает работать правиль- но, но ошибок не видно, проверьте правильность использования кавычек. Оставшаяся часть командного файла - это оператор case (строки 21-37), который имеет дело с числом аргументов командной строки. Если нет никаких аргументов, печатается сообщение об ошибке и мы выходим из программы. Мы ведь не можем делать никакого поиска командой grep, если мы даже не знаем, какую строку нужно искать. Если указан только один аргумент, то это должна быть строка по- иска. Это также означает, что в командной строке не указаны имена фай- лов и поэтому мы читаем их со стандартного устройства ввода, т.е. ко- мандный файл действует как фильтр. Цикл while (строки 26-29) читает со стандартного ввода имя каждого файла для поиска в нем командой grep нужной строки. Опция -y команды grep означает нечувствительность к ре- гистру символов при поиске. Использование этой опции дает нам хорошие шансы попасть на нужную строку. Другой интересной особенностью этого цикла являются две команды grep в строках 28 и 34. Мы ищем нужную строку не только в файле, но также в каталоге /dev/null. Это кажется странным? Да. Проблема заклю- чается в самой команде grep. Grep знает, когда в командной строке ука- зано более одного файла. Если имеется более одного файла, то имя файла выводится на экран до вывода найденной строки. Если же в командной строке указан только один файл, то его имя не выводится, поскольку считается, что пользователь должен помнить это имя. Это создает проб- лемы при написании командных файлов, когда вы имеете много имен фай- лов, но они обрабатываются по одному. Мы обрабатываем файлы по одному, поскольку если бы мы использовали указание группового имени файлов с помощью метасимволов, то могло бы оказаться слишком много имен файлов в одной командной строке для команды grep. Поэтому вместо использова- ния некоторой замысловатой схемы для сокращения количества аргументов, мы избегаем этого путем обработки в цикле каждого файла по очереди. Поскольку на самом деле мы ищем большое количество разных строк, то мы хотим видеть имена файлов, в которых они были найдены. При указании в командной строке grep каталога /dev/null, grep всегда печатает имя файла до вывода найденной строки в нашем цикле, поскольку теперь имеется два имени файла в качестве аргументов. Конеч- но, в /dev/null ничего нельзя найти, поскольку он пуст по определению. Последний образец в операторе case (строки 31-36) - это ловушка. Он соответствует любому количеству позиционных параметров сверх одно- го, что позволяет нам иметь переменное число каталогов в командной строке. Даже хотя мы можем указать несколько каталогов в командной стро- ке, первым аргументом по-прежнему является строка поиска. Не так легко сказать: "начиная со второго параметра", поэтому мы сохраняем строку поиска (в переменной STRING - Прим. перев.) и убираем ее командой shift. Теперь мы можем получить доступ к остальной части командной строки с помощью выражения $@. Давайте посмотрим, что происходит, когда мы ссылаемся на перемен- ную $OPT для получения опций команды find. Допустим, мы вызвали tgrep с опцией -c. Когда мы присваиваем значение переменной OPT, мы ставим строку c в виде "*.c" внутри двойных кавычек, поскольку мы не желаем, чтобы shell раскрывал эту строку (т.е. трактовал ее как имена всех файлов, соответствующих данному образцу) именно сейчас. Теперь, как только к переменной $OPT есть обращение в команде find, значением OPT является *.c, что означает поиск файлов, символьные имена которых соответствуют *.c. Для отмены символьной интерпретации мы должны использовать команду eval. Перед выполнением команды find команда eval заставляет shell повторно анализировать команду в отношении распрост- ранения значения переменной. На этом проходе выражение "*.c" превраща- ется в *.c, что разрешает генерацию имен файлов таким образом, чтобы были просмотрены все эти файлы. Указывая $@ в команде find, мы можем производить поиск во всех каталогах сразу. Таким образом, нам нужно вызвать find только один раз, что позволяет сберечь время центрального процессора. Одной из ин- тересных проблем, возникших при разработке данного командного файла было то, что выражение вида $* не работало. В команде find возникала ошибка сохранения. Даже запуск shell'а в режиме -x (установленный флаг разрешения выполнения) не разрешил проблему. Изменение синтаксиса, ка- жется, помогло разобраться с ней. Оказывается, причина в том, что вы- ражение $* раскрывается в "$1 $2 ...", в то время как выражение $@ превращается в "$1" "$2" (т.е. в отдельные аргументы). Происходило то, что выражение $* передавало имена нескольких каталогов команде find как одну строку. Команда find не могла обнаружить файл такого типа, поэтому прекращала выполнение. Когда же вместо этого было использовано выражение $@, команда find получила несколько независимых строк с име- нами. Это вполне подошло команде find, и все заработало. Такие мелкие детали всегда требуют много времени для изучения! ВОЗМОЖНЫЕ ИССЛЕДОВАНИЯ В чем разница между двумя следующими операторами? grep "$1" `find "$2" -print` и find "$2" -print | while read F do grep "$1" $F done Они кажутся совершенно похожими, но существует различие в глав- ном. Первый оператор - это один вызов команды grep. Аргументами явля- ются множество имен файлов, поставляемых командой find. Если find сге- нерирует слишком много имен файлов, то выполнение команды завершится. О том, что сгенерировано слишком много имен файлов, никакого предуп- реждения не выдается, но большое количество файлов фатально для grep. Поэтому мы должны рассматривать такой синтаксис как недопустимый в об- щем случае. Второй оператор - это цикл. Он работает медленнее и вызывает grep много раз, что забирает много времени центрального процессора. Однако положительным моментом является то, что цикл получает данные по конве- йеру, который фактически не имеет ограничений на число данных, которое он может иметь. Наша программа никогда неожиданно не прекратит выпол- нение. 2.1.4. paths - нахождение пути доступа к исполняемым файлам, со специальными опциями ------------------------------------------------------------ ИМЯ: paths ------------------------------------------------------------ paths Определитель маршрутных имен файлов со специальными опциями НАЗНАЧЕНИЕ Выводит на экран каталог, в котором располагается файл, выдает имя файла в длинном формате или ищет файлы с установленным битом поль- зовательского идентификатора (setuid bit files) в каталогах по указан- ному маршруту. ФОРМАТ ВЫЗОВА paths [-l] [-s] file [file ...] ПРИМЕР ВЫЗОВА $ paths -l ed ex vi Выдает в длинном формате имена файлов, которые являются исполняе- мыми модулями редакторов ed, ex и vi ТЕКСТ ПРОГРАММЫ 1 : 2 # @(#) paths v1.0 Path locator with special options Author: Russ Sage 2а Определитель местонахождения файлов со специальными опциями 4 FORMAT="path" 6 for ARG in $@ 7 do 8 if [ '`echo $ARG | cut -c1`" = "-" ] 9 then case $ARG in 10 -l) FORMAT="ls" 11 shift;; 12 -s) FORMAT="set" 13 set "1";; 14 *) echo $0: arg error" >&2 15 echo "usage: $0 [-l] [-s] file [file ...]" >&2 16 exit 1;; 17 esac 18 fi 19 done 21 IFS="${IFS}:" 23 for FILE in $@ 24 do 25 for DIR in $PATH 26 do 27 case $FORMAT in 28 path) if [ -f $DIR/$FILE ] 29 then echo $DIR/$FILE 30 fi;; 31 ls) if [ -f $DIR/$FILE ] 32 then ls -l $DIR/$FILE 33 fi;; 34 set) echo "\n:::::::::::::::::::" 35 echo "$DIR" 36 echo "::::::::::::::::::::" 37 ls -al $DIR | grep "^[^ ]*s[^ ]*";; 38 esac 39 done 40 done ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ ARG Содержит каждый аргумент командной строки DIR Элемент с именем каталога в переменной PATH FILE Содержит имя каждого файла в командной строке FORMAT Тип требуемого формата выходных данных IFS Переменная shell'а, разделитель полей PATH Переменная shell'а, пути к каталогам исполняемых модулей ОПИСАНИЕ ЗАЧЕМ НАМ НУЖЕН КОМАНДНЫЙ ФАЙЛ paths? В нашей среде интерпретатора shell переменная с именем PATH со- держит имена каталогов, отделенные друг от друга символами двоеточия (:). Каждый раз, когда вы вводите команду после приглашения shell'а, интерпретатор shell, начиная с первого каталога, указанного в перемен- ной PATH, смотрит, находится ли введенная вами команда в этом катало- ге. Если да, то команда выполняется. Если нет, то shell идет в следую- щий каталог, указываемый переменной PATH, и так далее, пока не будут проверены все каталоги. Если команда все же не будет найдена, то вы получите следующее сообщение об ошибке: ------------------------- | | $ whatchamacallit | sh: whatchamacallit: not found | | Такой поиск команды осуществляется автоматически, но сама система не сообщает вам, ГДЕ размещена команда. Нам необходима утилита, кото- рая ищет и выводит на экран маршрут к указанному файлу. Имея такую утилиту, вы можете использовать кратчайшие пути получения того, что вам нужно, и выполнять множество различных трюков. Вы можете комбини- ровать подобную команду с другими командами для создания гораздо более мощных средств. С маршрутом можно также комбинировать команды more, ls, file и cd системы UNIX. Возможно, вы обнаружите и другие команды по мере экспериментирования. Команда, несколько похожая на ту, которую мы ищем, существует где-то в мире системы UNIX Systev V. Например, в системе AT&T это ко- манда where. В системе UNIX Berkeley это команда which (текст на языке Си-shell'а) или whereis (исполняемая программа). Whereis дает дополни- тельную информацию, такую как место размещения файлов с исходными текстами (в каталоге /usr/src). Увидев, как мы создаем нашу собствен- ную команду поиска маршрута, вы можете модифицировать ее для обеспече- ния работы с особенностями некоторых других команд и приспособить та- кие вещи к вашим нуждам. Прежде чем мы удовлетворим свои прихоти, давайте бегло глянем на команду path, более простую, чем paths. Вся программа выглядит пример- но так: IFS="${IFS}:" for FILE in $@ do for DIR in $PATH do if [ -f $DIR/$FILE ] then echo $DIR/$FILE fi done done Основная идея очень проста. Сперва мы добавляем двоеточие (:) к разделителю полей. Нам необходимо сохранить значения, принятые по умолчанию (пробелы, табуляции, символы новой строки), так, чтобы мы могли все-таки обрабатывать командную строку с пробелами в качестве символов-разделителей. Символ : дает нам возможность отдельно рассмат- ривать каждый маршрут, хранимый в переменной PATH. Вся программа представляет собой два цикла for. Внешний цикл просматривает имена всех файлов, указанных в командной строке. Внут- ренний цикл последовательно обходит все каталоги, содержащиеся в пере- менной PATH. Для каждого файла просматриваются все каталоги с целью определения, содержит ли этот каталог файл с таким именем. Полное маршрутное имя представляет собой комбинацию префикса-каталога и имени файла (называемых именем каталога и базовым именем соответственно). Встроенная shell-команда test использована для определения того, су- ществует ли файл в определенном каталоге. Если ваша переменная PATH выглядит так: PATH=.:/bin:/usr/bin:/etc/:$HOME/bin то внутренний цикл выполнит пять итераций в таком порядке: ., /bin, /usr/bin, /etc и, наконец, $HOME/bin. Если бы запрос имел вид "path ll" для поиска утилиты, которую мы создадим позже в этой главе, то ре- зультат мог бы выглядеть так: --------------------------------------------- | | /usr/bin/ll | /usr/russ/bin/ll | | Это значит, что команда ll была найдена в двух местах из вашего набора маршрутов поиска. ЧТО ДЕЛАЕТ paths? Теперь, когда мы знаем, как работает более простая команда path, мы можем по достоинству оценить дополнительные возможности специальной команды получения маршрута - команды paths. Paths имеет три основные функции. Она может выполняться как основная команда path, которую мы уже рассмотрели, и давать полное маршрутное имя исполняемого модуля. Она может выдавать маршрут файла в длинном формате. Она также может выдать список всех файлов с установленным пользовательским идентифика- тором (setuid bit files), которые есть в ваших маршрутных каталогах. (См. главу 8, где описаны биты setuid.) Синтаксис вызова немного отличается для разных опций. Для того чтобы использовать формат выдачи маршрута или формат команды ls, нужна такая командная строка: paths [-l] file [file ...] как, например, в командах "paths ls who date" или "paths -l ll". Для поиска файлов с установленным пользовательским идентификатором (setuid files) не нужно указывать имена файлов в командной строке. Вся команда до