ПРИЕМЫ ПРОФЕССИОНАЛЬНОЙ РАБОТЫ В UNIX

ГЛАВА 2

ДОСТУП К ФАЙЛАМ

СОДЕРЖАНИЕ





ВВЕДЕНИЕ

В главе 1 был представлен обзор общей структуры системы UNIX и показано, как взаимодействуют ее различные части. Это похоже на введение в географию, когда на глобусе показывают континенты и крупные водные пространства. Такая информация, хотя и является хорошим фундаментом для общих знаний, вряд ли поможет найти наилучший путь из Сан-Франциско в Лос-Анжелес. Необходим следующий уровень детализации: названия поселений, дорог, развилок, улиц, адресов.
Файловая система UNIX похожа на континент со множеством городов и, действительно, с адресами внутри городов. Каталоги и различные уровни подкаталогов можно сравнить с маршрутами между различными пунктами назначения, названия файлов - с адресами. Большое число путей и мест назначения может выглядеть пугающе, но благодаря регулярности и логичности, файловая система UNIX позволяет вам легко перемещаться из одного места в другое, если вы знаете несколько основополагающих принципов.
Будучи пользователями UNIX, все мы научились пользоваться основными командами файловой информации, как, например, ls с различными опциями. Мы знаем, как перемещаться между каталогами и копировать или перемещать файлы. Тем не менее, находить нужную информацию о файлах из всей массы информации не так-то легко. Нам необходимо создать инструментальные средства, которые используют древовидную структуру файлов в UNIX, чтобы находить то, что мы ищем, и, соответственно, выводить информацию о файлах на экран, печатать листинги содержимого файлов и т.д.
Эта глава знакомит с инструментальными средствами, которые облегчают задачу поиска и доступа к файлам. Доступ к файлам может быть обеспечен различными способами, поэтому техника и стиль меняются от одного командного файла к другому. Например, в некоторых случаях вам нужно найти имена всех файлов в данном сегменте файлового дерева, в других случаях вас будут интересовать файлы только заданного типа: текстовые файлы вообще или исходные файлы на языке Си в частности.


КОМБИНИРОВАНИЕ ПРОДУКТИВНЫХ ИДЕЙ


Две концепции являются общими почти для всех файловых инструментальных средств. Первая - это идея рекурсивного поиска, которая означает, что некоторые команды системы UNIX (например, find) просматривают все файловое дерево, начиная с некоторой заданной начальной точки (или с текущего каталога). Если в данном каталоге встречается подкаталог, то его содержимое тоже исследуется - и так далее вниз к самому нижнему подподкаталогу. Так проходятся маршруты ко всем файлам в целом дереве.
Стандартные команды системы UNIX обеспечивают только ограниченное число основных функций, которые могут работать рекурсивно по всему файловому дереву. Наша стратегия при создании инструментальных средств в этой главе - воспользоваться преимуществами такого рекурсивного поиска и распространить их на многие другие функции.
Вторая ключевая идея, связанная с полезными файловыми инструментальными средствами - это возможность соединения команд с программными каналами и управление потоком данных с помощью переадресации. Вероятно, вы уже встречались с подобными особенностями в вашей собственной работе с UNIX и эффективно их использовали. Возможно, вы еще не осознали, что соединение рекурсивного поиска, предоставляемого некоторыми стандартными командами, со специфическими функциями, предоставляемыми другими командами, позволяет нам создать команды, которые автоматически обходят обширные файловые деревья и извлекают нужную информацию. (В следующей главе мы выйдем за пределы распечатки и отображения информации на экран и научимся работать с файлами так, что мы сможем копировать, перемещать и восстанавливать их по мере надобности.)
Для удобства мы сгруппируем инструментальные средства в два раздела: поиск файлов и распечатка файловой информации. Имеет смысл представлять их в таком порядке, так как вы сначала должны найти файл, чтобы потом с ним работать.


ПОИСК ФАЙЛОВ

Этот раздел посвящен поиску файлов, где бы они ни находились, выводу на экран выбранной информации и поиску символьных строк внутри файлов.
Первая программа, tree, обходит все файловое дерево и печатает имена всех файлов в формате визуального дерева. Она рекурсивно спускается в каждый каталог и находит все его файлы, обеспечивая тем самым глобальный осмотр файловых областей и их вложенной по глубине структуры.
Другое инструментальное средство - это thead. Thead печатает несколько первых строк текстовых файлов, которые находятся в данном сегменте файлового дерева. Просматривая заголовок, т.е. первые несколько строк файла, вы можете получить достаточно информации, чтобы идентифицировать содержимое файла. При вызове thead вы можете явно задать каталог либо передать команде thead по конвейеру список полных имен файлов. Это делает команду thead фильтром - особым видом команд системы UNIX, который мы обсудим позже.
Следующее инструментальное средство - tgrep. Как следует из названия, это еще одна команда, связанная с файловым деревом, которая использует утилиту grep. Tgrep ищет символьные строки в каждом файле, который находится в данном сегменте файлового дерева. Tgrep также является фильтром, так что имена файлов можно передавать ей по конвейеру.
В нашем последнем проекте в этом разделе мы обратимся к использованию каталогов как средства "навигации". Сначала мы опишем основной алгоритм для утилиты, которая для каждого файла из заданного списка файлов проверяет, находится ли этот файл в каком-либо каталоге по указанному маршруту поиска. Затем мы построим paths - утилиту, которая дополняет функцию поиска полезными опциями.

РАСПЕЧАТКА ФАЙЛОВОЙ ИНФОРМАЦИИ

Этот раздел знакомит вас с инструментальными средствами, предназначенными для вывода на экран имен файлов и их содержимого. Инструменты такого рода весьма полезны, так как они могут значительно уменьшить количество необходимых символов, набираемых с клавиатуры при запуске команды, и внести больше смысла в одну команду.
Первые два командных файла являются пре- и постпроцессорами для команды ls. Команда lc выводит файловую информацию по столбцам, команда ll перечисляет файлы в длинном формате. Эти командные файлы дополнены опциями команды ls, чтобы сделать распечатки более информативными. Так как команда ls используется довольно часто, упаковка наиболее часто применяемых нажатий клавиш в командные файлы представляется целесообразной. Упаковка уменьшает количество постоянно набираемых символов и упрощает использование команд, исключает необходимость запоминания подробного синтаксиса.
Третье инструментальное средство - это kind. Kind - еще один командный файл препроцессорного типа, использующий команду UNIX file. Команда file читает указанный файл и затем сообщает, является ли этот файл текстовым, архивным или исполняемым. Поскольку распечатки команды file не выбирают файлы заданного типа, возникает необходимость в создании для этого специальной утилиты. Команда kind работает с распечаткой команды file. Kind выводит на экран имена файлов только заданного типа.
Еще один командный файл - m, который облегчает работу со стандартной командой more системы UNIX, уменьшая количество необходимых для запуска команды символов и упрощая интерфейс. Делается это без потери гибкости: так же, как вы можете использовать команду more для файла или передать команде more данные по программному каналу, вы можете сделать то же самое для m.
Следующий командный файл - это mmm. Он состоит из одной заготовленной командной строки для программы nroff системы UNIX. Существует много способов вызова команды nroff и множество различных опций к ней. Если же вы редко используете nroff, у вас могут возникнуть трудности в запоминании специфических опций, необходимых для вашей работы с командой. Эти проблемы отпадут, если у вас есть команда mmm. Определите опции, которые вы обычно используете, и введите их в командный файл mmm (о том, как это сделать практически, речь пойдет ниже). Теперь достаточно набрать mmm - и вы имеете возможность работать с вашей командой nroff.
Последняя утилита - pall. Pall обходит файловое дерево, ведя поиск файлов заданного типа, и готовит их к выводу на принтер. Команда pr системы UNIX используется для разбивки на страницы всех файлов вместе и включения заголовков. Эта команда предлагает на рассмотрение принтеру один большой файл и наиболее полезна в тех случаях, когда у вас имеется множество каталогов с текстовыми файлами или с исходными файлами программ.
Определив в общем основные наши задачи, перейдем к более близкому знакомству с упомянутыми инструментальными средствами.


2.1. ПОИСК ФАЙЛОВ

2.1.1. tree - визуализация файлового дерева

----------------------------------------------------
ИМЯ: TREE
----------------------------------------------------

tree - вывод на экран структуры файлового дерева

НАЗНАЧЕНИЕ


Находит все файлы в файловом дереве и выводит на экран имена файлов, показывая иерархическую структуру файлового дерева.

ФОРМАТ ВЫЗОВА

tree [dir]

ПРИМЕР ВЫЗОВА

$ tree $HOME
Выводит структуру файлового дерева регистрационного каталога.

ТЕКСТ ПРОГРАММЫ


1  :
2  # @(#) tree v1.0  Visual display of a file tree Author: Russ Sage
2а                   вывод на экран структуры файлового дерева
3
4  if [ "$#" -gt 1 ]
5    then echo "tree: wrong arg count">&2
6         echo "usage: tree [dir]"    >&2
7         exit 2
8  fi
9  if [ "$#" -eq 1 ]
10 then if [ ! -d $1 ]
11   then echo "$0: $1 not a directory">&2
12        echo "usage: tree [dir]"     >&2
13        exit 2
14         fi
15 fi
16
17 find ${1:-.} -print | sort | sed -e "1p" -e "1d"          \
18                                  -e "s|[^/]*/|      /|g"  \
19                                  -e "s|[^ */|/|"          \
20                                  -e "s|/\([^/]*\)$|\1|"

ОПИСАНИЕ

ЗАЧЕМ НАМ НУЖЕН КОМАНДНЫЙ ФАЙЛ tree?

Как мы уже отмечали, вся система UNIX строится вокруг файловой системы, которая похожа на дерево. Дерево, с которым мы работаем в системе UNIX, растет вверх ногами: корень находится вверху, а ветви и листва растут вниз от корня. Физическая структура реальных деревьев и файловых деревьев, используемых в системе UNIX, очень сходна: один корень (начальная точка) и один ствол. Как глубоко и как далеко могут уходить ветви от основного ствола - не ограничивается ничем, кроме ограничений физического пространства. Аналогично, число листьев, которые может иметь каждая ветвь, фактически не ограничено.
Многое в системе UNIX задумано для того, чтобы приспособиться к дереву. Некоторые команды обходят дерево и сообщают о его компонентах, но обычно их сообщения выдаются в форме, не очень удобной для чтения человеком. Это делает командные файлы весьма мощными инструментами. Перевести необработанные, недружественные сообщения командных файлов в удобный, информативный вид довольно легко.
Команда tree является комбинацией команд системы UNIX, которые представляют логическую файловую структуру в наглядной форме. Эта команда полезна для получения глобальной картины файлов, их расположения в иерархической структуре файлового дерева, гнездовой структуры каталогов и подкаталогов.

ЧТО ДЕЛАЕТ tree?

Команда tree - это постпроцессор для команды UNIX find. Find просматривает сегмент файлового дерева и полные имена всех файлов, которые соответствуют заданному критерию. Команда tree использует утилиту sed системы UNIX, чтобы перевести выход команды find в наглядную форму.
Входным параметром для команды tree является имя каталога, которое может быть указано в любом абсолютном виде, например, /usr/spoOL/uucp, или в относительном, например, ../../bin. Если никакого имени не указано, подразумевается ., что является текущим каталогом.
Имя каталога является началом (или корнем) отображаемого дерева. Чтобы показать глубину дерева, все файлы, подчиненные данному каталогу, отображаются с отступом. Для удобства представления гнездовой структуры, между следующими друг за другом ответвлениями печатается косая черта (/).
Рассмотрим пример структуры каталога. Пусть корневым каталогом будет /tmp с двумя каталогами: a и b. В каталоге a находится подкаталог aa, который содержит файл file1, а в каталоге b , соответственно, подкаталог bb, содержащий файл file2. Команда find выдаст распечатку такого вида:

# find /tmp -print
/tmp
/tmp/a
/tmp/a/aa
/tmp/a/aa/file1
/tmp/b
/tmp/b/bb
/tmp/b/bb/file2
Как видно из этого листинга, файлы a и aa есть каталоги, а файл file1 находится внизу файлового дерева. Сравните этот результат с результатом, который выдает команда tree, используя утилиту sed.

# tree /tmp
/tmp
/     a
/     /     aa
/    /      /      file1
/    b
/    /     bb
/    /      /      file2

Корневым каталогом в этом листинге является каталог /tmp. Там, где дерево переходит на более глубокий уровень, печатаются только символы косой черты. Первый уровень - /tmp, под этим уровнем находятся файлы-каталоги a и b, затем, соответственно, их подкаталоги aa и bb. Исходя из этого листинга, мы делаем вывод, что на первом уровне каталога находятся два файла (и эти файлы в действительности являются каталогами) и что два файла находятся в подчиненных каталогах. Отметим, что мы смогли идентифицировать aa и bb как каталоги только потому, что в них присутствуют файлы file1 и file2.
Сравните этот листинг с выходом "необработанной" команды find. Выход команды tree исключает отвлекающее внимание повторение элементов путей доступа при каждом переходе к более низкому уровню. Благодаря этому, сразу же видно СУЩЕСТВЕННУЮ информацию. Вот что мы имеем в виду, когда говорим о создании более наглядного для человека интерфейса с системой UNIX.

ПРИМЕРЫ

ПОЯСНЕНИЯ

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

НАЗНАЧЕНИЕ

Пройти файловое дерево и напечатать первые несколько строк каждого файла. Если не указан каталог, то 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а                    Печатает заголовки файлов в дереве
3
4 if [ "`echo $1|cut -c1`" = "-" ]
5  then  echo "$0: arg error"
6        echo "usage: $0 [dir ...]"
7        exit 1
8   fi
9
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

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ


ОПИСАНИЕ

ЗАЧЕМ НУЖЕН КОМАНДНЫЙ ФАЙЛ thead?

Как уже объяснялось ранее в этой главе, иерархическая файловая система является очень значительной частью системы UNIX. Однако, только несколько команд в UNIX имеют дело непосредственно с рекурсивным поиском файлов. Единственный способ расширить возможности системы - создать новые рекурсивные утилиты, работающие с файлами. В данном случае мы соединим нашу стратегию поиска по дереву с командой head системы UNIX для упрощения идентификации содержимого всех файлов в выделенном сегменте файлового дерева.
Иногда у нас, возможно, будет возникать желание просмотреть файлы в более чем одном каталоге. В больших проектах разработки программного обеспечения файлы обычно создаются в нескольких иерархических каталогах. Thead может работать со множеством путей доступа и выводить заголовки (несколько первых строк файлов) в виде непрерывного потока.

ЧТО ДЕЛАЕТ thead?

Thead - это препроцессорная команда к команде head системы UNIX. Команда head очень примитивна, но, добавляя к ней управляющую структуру и логику, мы можем создать очень полезное инструментальное средство, которого нет в стандартной среде UNIX.
Например, мы захотели просмотреть заголовки всех текстовых файлов в нашем регистрационном каталоге. Если у нас имеется большое число подкаталогов, нам необходимо все их пройти и просмотреть все файлы, содержащиеся в них. Мы можем сделать это с помощью команды:
$ thead $HOME
Если мы хотим просмотреть только исходные файлы на языке Cи (*.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 к исполняемым модулям приводит к выводу "таинственных" символов на экран и иногда может вызвать дамп оперативной памяти.

ПРИМЕРЫ

ПОЯСНЕНИЯ

Строки 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 действует как фильтр.

ФОРМАТ ВЫЗОВА

grep [-c|-h] string [file ...]

ПРИМЕР ВЫЗОВА

# tgrep "profanity" /
Поиск слова "profanity" по всей системе (суперпользователь снова на тропе войны!)

ТЕКСТ ПРОГРАММЫ


1   :
2   # @(#) tgrep v1.0  Search for string in tree Author: Russ Sage
2а                     Поиск строки в дереве
3
4   OPT=""
5
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
20
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

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ОПИСАНИЕ

ЗАЧЕМ НАМ НУЖЕН КОМАНДНЫЙ ФАЙЛ 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 является фильтром.

ПРИМЕРЫ

ПОЯСНЕНИЯ

В строке 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а    Определитель местонахождения файлов со специальными опциями
3
4   FORMAT="path"
5
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
20
21  IFS="${IFS}:"
22
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

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ОПИСАНИЕ

ЗАЧЕМ НАМ НУЖЕН КОМАНДНЫЙ ФАЙЛ 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) не нужно указывать имена файлов в командной строке. Вся команда должна быть такой:
paths -s
Формат setuid и форматы выдачи маршрута являются взаимоисключающими, поскольку предполагается, что вы хотите узнать от компьютера, какие и где находятся файлы, а не отгадывать имена файлов.
Если в командной строке находится какая-то другая опция, то в стандартный вывод выводится сообщение об ошибке и командный файл завершается. Опции устанавливают флаг формата вывода в одно из трех значений. Весь дальнейший вывод из командного файла управляется выбранным форматом вывода.

ПРИМЕРЫ

ПОЯСНЕНИЯ

В строке 4 инициализируется переменная FORMAT, указывая маршрутный тип поиска. Выполняется действие по умолчанию, точно такое же, как в командном файле path, который мы рассмотрели ранее.
В строках 6-19 все аргументы командной строки проверяются на корректность. Критерием того, что аргумент есть опция, является дефис в роли первого символа. Заметим, что здесь не разрешено использование синтаксиса "-xyz". Это заставляет вас пользоваться синтаксисом "-x -y -z". Хотя этот момент может показаться несущественным, на самом деле он важен. Всегда нужно достигать компромисса между быстрой разработкой командного файла при согласии на недостатки жесткого синтаксиса - и разрешением гибкого формата за счет дополнительных усилий по кодированию и отладке и за счет более медленного выполнения. Ваш выбор зависит от ваших приоритетов, от количества людей, использующих ваше инструментальное средство, и от того, насколько критична скорость выполнения. Конечно, если скорость критична, вы, вероятно, захотите использовать каким-то образом язык Си. Мы оставляем обработку конкатенированных опций в качестве упражнения для читателя.
Цикл for проходит по всем позиционным параметрам. Если первым символом аргумента является "-", то он сверяется со списком допустимых аргументов с помощью оператора case в строках 9-17. Опция "-l" изменяет переменную формата, после чего убирается из рассмотрения. Это делается для освобождения этой позиции, чтобы конечным результатом были просто имена файлов в командной строке.
Опция "-s" также изменяет переменную формата. Однако, вместо того, чтобы убрать опцию из командной строки, она ликвидирует всю командную строку и заменяет ее символом "l". Это заставляет цикл for проходить только одну итерацию, так как в командной строке теперь только один параметр. Благодаря такому обращению с командной строкой, нам не нужен другой цикл: мы можем использовать тот же цикл, что и в определении маршрута, без всяких модификаций. Поскольку после опции s не ожидается никаких имен файлов, мы больше не хотим рассматривать командную строку.
Если использована опция, которая не является ни l, ни s, то этой опции соответствует звездочка (*) и в стандартный файл ошибок выводится сообщение об ошибке. Затем командный файл завершается.
Вы бы могли просто проверить первый параметр командной строки, чтобы выяснить, является ли он опцией, и если является, то установить эту опцию. Поскольку можно использовать только одну опцию за один раз, мы могли бы предполагать, что в остальной части командной строки были имена файлов. Тем не менее, этот цикл допускает простое добавление других опций, которые могли бы действовать в дополнение к одной основной. Это более предпочтительно, и оно не влияет на производительность.
В строке 21 мы добавляем символ двоеточия (:) к другим символам разделителя полей. Мы должны именно добавить двоеточие, а не превратить разделитель полей только в двоеточие. Если бы мы сделали последнее, то это запутало бы разбор имен файлов в командной строке.
Основной цикл представлен в строках 23-40. Это двойной цикл for. Внешний цикл проходит по каждому файлу в командной строке, а внутренний цикл обрабатывает каждый каталог, указанный в вашей переменной PATH. Обратите внимание, что внешний цикл идет по именам файлов, а не по записям каталогов. Если бы мы выбрали второе, то в распечатке нарушился бы порядок имен файлов, поскольку поиск шел бы сначала по каталогам.
Следовательно, для каждого имени файла и каталога действие зависит от требуемого формата. Маршрутный формат печатает полное имя, листинговый формат выполняет команду ls, а формат set не ищет указанные имена файлов, но проверяет права доступа и ищет файлы с установленным пользовательским идентификатором.


Обрат


ПРИЕМЫ ПРОФЕССИОНАЛЬНОЙ РАБОТЫ В UNIX


аналогами. Опция ls есть дополнение, которое сокращает объем работы при вызове. Наличие комбинации поиска и команды ls освобождает того, кто вызывает этот командный файл от необходимости применять команду подстановки. Старая и новая команды выглядят примерно так:
ll `path ll`
Находит путь к ll, а затем запускает на нем команду ls -l.

paths -l ll

Находит путь и вместо того, чтобы его напечатать, выполняет команду ls -l применительно к этому пути.
Формат setuid в строке 34 прощается с подходом "один файл за один раз" и включает каталоговую машину. Поскольку внешний цикл установлен на одну итерацию, внутренний цикл становится главным. Для каждого каталога, указанного в PATH, печатаются оформление из двоеточий и имя каталога. Это делает распечатку приятной, информативной и наглядной.
Ключевой командой является комбинация ls-grep. Каждое имя файла в каталоге распечатывается в длинном формате, затем просматривается бит установки пользовательского идентификатора. Модель такова, что команда ls -al $DIR печатает следующее:

-------------------------------
|
|  -rws--x--x   1 root    bin       16235 Sep 13  1985 /bin/su
|
|
Аргумент "^[^ ]*s[^ ]*" означает поиск от начала строки символа, отличного от пробела, за которым следует один или более символов, отличных от пробела, затем символ s и затем один или более символов, отличных от пробела. Это выражение ограничивает поиск битами прав доступа в начале строки. Если имеется символ s где-либо в правах доступа (либо в пользовательском идентификаторе процесса, либо в групповом идентификаторе процесса), то команда grep отрабатывает успешно и печатается вся строка.
Такой вид поиска установленного пользовательского идентификатора несколько "легковесен" в том смысле, что поиск ведется только согласно переменной PATH, которая у вас есть. Файлы с установленным пользовательским идентификатором могут находиться в каталогах, которые не указаны в PATH. Однако в такой реализации данная опция обеспечивает быстрое обращение к вашим локальным файлам с установленным пользовательским идентификатором.

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

Данный командный файл открыт для многих различных видов модификации. Поиск полного имени файла является фундаментальной задачей программного обеспечения по сопровождению файлов. Эта возможность позволяет нам полагаться на саму программу paths или использовать paths в качестве куска более объемной программы.
При разработке ваших собственных программ следует обратить внимание на гибкость командного файла paths, которая выражается в отличии между обрабатываемыми форматами. Первые два формата используют отдельные файлы, а формат set использует каталоги. Дальнейшие дополнения к командному файлу paths могут касаться любой из этих строк или могут комбинировать их. Если есть необходимость, программное обеспечение может приспособиться к этому.


2.2.ВЫВОД ИНФОРМАЦИИ

2.2.1. lc - вывод файловой информации на экран по столбцам

-------------------------------------------------------------
ИМЯ: lc
------------------------------------------------------------

lc Выдает список файлов в колоночном формате

НАЗНАЧЕНИЕ

Выдает информацию о файлах в формате колонок, показывая каталоги и исполняемые модули. Этот листинг можно пропустить через команду more.

ФОРМАТ ВЫЗОВА

lc [-m] [ls options] file [file ...]

ПРИМЕР ВЫЗОВА

lc -R $HOME
Выдает список всех файлов во всех подкаталогах моего регистрационного каталога.

ТЕКСТ ПРОГРАММЫ


1   :
2   # @(#) lc v1.0   List files in a cOLumn   Author: Russ Sage
2а                     Выводит список файлов в колоночном виде
3
4   if [ "$1" = "-m" ]
5     then  MORE="| /usr/bin/more"
6           shift
7     else  MORE=""
8   fi
9
10   eval "/bin/ls -a $@ | /bin/pr -5t" $MORE   # pre System V
11   eval /bin/ls -aCF $@ $MORE                 # System V

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ОПИСАНИЕ

ЗАЧЕМ НАМ НУЖЕН КОМАНДНЫЙ ФАЙЛ lc?

В мире компьютеров многие люди изобретают колесо, а другие люди изобретают его снова. Если первое колесо не того размера или не того цвета, делается другое колесо. В нашей конкретной ситуации исходным колесом является команда ls системы UNIX, которая имеет некоторые недостатки в своих ранних реализациях. Она выдает хорошую информацию, но она печатает имена файлов только в одну колонку, что приводит к нерациональному расходованию места и затрудняет чтение имен файлов. Поэтому мы создаем версию команды ls, которая отображает распечатки в несколько колонок.
Как видно из предыдущего листинга, lc имеет две формы. Одна предназначена для систем, более ранних, чем System V, а другая - для System V и последующих версий UNIX. Причина в том, что System V версии 2 имеет новую команду ls, которая делает именно то, что мы хотим. Система Berkeley также имеет версию команды ls, которая по умолчанию использует несколько колонок при выводе на терминал. Но для XENIX и ранних версий System V мы должны делать это сами. Дело в том, что хотя в вашей версии UNIX, XENIX или чего-либо еще могут отсутствовать команды, имеющиеся в других версиях, вы обычно можете построить то, что вам нужно. Это может потребовать определенных усилий, и ваши программы могут работать не так быстро и не так эффективно, но вы МОЖЕТЕ получить нужное средство.
Пользователям интерпретаторов csh и последнего sh, имеющего функции, видимо, лучше бы заменить весь этот сценарий на то, чтобы сделать lc псевдонимом (alias). Использовать возможность введения псевдонимов, чтобы присвоить имя любой корректной командной строке UNIX (например, вызову команды ls с указанными опциями). Это легче, чем писать командный файл, но ограничивает вас необходимостью работать с уже имеющимися командами или опциями. Это быстрее, так как не создается никаких дополнительных процессов.
При работе со старым интерпретатором sh мы должны пройти через обычную процедуру изготовления командного файла и размещения его в каталоге bin. С другой стороны, SCO XENIX System V решает эту проблему, связывая эти же имена (lc, lf, l) с обычной командной ls и используя вызывающее имя для определения формы распечатки.
Итак, зачастую имеется много альтернатив. Мастера UNIX, сталкиваясь с какой-либо проблемой, не борются с ней с помощью Си или командного файла интерпретатора shell. Поскольку они знакомы с существующими ресурсами системы UNIX, они могут рассмотреть проблему и выбрать стратегию, использующую наименее сложное средство, выполняющее данную работу с приемлемым уровнем производительности. В порядке возрастания сложности, это могут быть непонятная, но существующая команда и/или опция, псевдоним, командный файл интерпретатора shell или программа на языке Си.

ЧТО ДЕЛАЕТ lc?

Общий подход к разработке этой команды заключается в том, чтобы собрать вместе некоторые опции и сделать новую команду с более мощным интерфейсом. Чтобы достичь этой мощи, мы можем сделать пре- или постпроцессор для обычной команды системы UNIX.
Главная задача здесь - печать колонок, поэтому мы смотрим на опции команды ls, чтобы задействовать их. Конечно, мы включаем опцию -C. Какие еще опции ls нам нужны? Обычно UNIX не печатает файлы, имена которых начинаются с точек, например,.profile, если только вы не указываете ls -a. Это забывается при просмотре этих важных файлов, поэтому мы конструируем нашу команду так, чтобы она печатала их по умолчанию. Никакие файлы не скрываются от нас. Для пользователей System V и BSD (или для любого, кто имеет опцию -F), листинг улучшается за счет вывода "/" после имени каталога и "*" после исполняемого файла. Ранняя команда ls системы UNIX не имела возможности печатать в таком стиле. Отметим, что данное использование термина "исполняемый" означает показ того, что флаги прав доступа имеют бит "x", а не то, что это файл типа a.out с магическим числом. Это отличие важно тем, что делает наш командный файл более полезным.
Если ожидается длинная распечатка, как это бывает обычно для рекурсивных каталогов, то вы хотите иметь доступ к команде more. Мы встраиваем команду more так, чтобы ее можно было активировать с помощью опции -m. Опция -m должна быть первой опцией после имени команды, из-за способа, которым она проверяется внутри программы. Если она передается после первой опции, она переходит к команде UNIX ls и интерпретируется как печать в потоковом формате. Это такой формат, в котором все имена расположены в строках, разделенных запятыми (,). Как мы уже отмечали, вы можете сделать интерфейс этого командного файла более гибким за счет дополнительной работы над ним.

ПРИМЕРЫ

ПОЯСНЕНИЯ

В строках 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 и поместить его в конец командной строки, находящейся в кавычках. Несколько непонятно, но если вы думаете об этом пару лет, оно становится осмысленным.
Для тех из вас, кто имеет новую команду ls (System V, версия 2 или BSD 4.2), не требуется два конвейера в команде. Как показывает строка 11, мы получаем подходящий колоночный формат из самой команды ls, вместе с показом всех файлов и специальными символами / и * для каталогов и исполняемых файлов. Обозначение $@ относится ко всему содержимому командной строки, т.е. к вашим дополнительным опциям команды ls и к именам файлов, список которых вы хотите распечатать. Поступая таким образом, мы создаем фундамент (опции a,C,F), а вы можете надстраивать его (используя опции R,t и т.д.). Скромный, но элегантный фокус заставить ls сообщить свои опции заключается в том, чтобы вызвать ее с неверной опцией. Большинство команд не используют опции z или ?, поэтому вызов "ls -z" или "ls -?" приведет к такому результату:

--------------------------------
|
|   ls: illegal option -- z
|   usage:  -1ACFRabcdfgilmnopqrstux [files]
|
Все эти опции представляют определенный интерес. Если вы часто используете какие-либо из них, поместите их в командный файл lc, и вы получите вашу собственную адаптированную команду.
Вы обратили внимание, что все обычные команды системы UNIX, используемые в нашем командном файле, имеют полные маршрутные имена? Это может показаться несколько странным, но причина указания полных маршрутных имен в том, что когда shell запускает команду, он не должен возвращаться к анализу переменной PATH и искать, где расположена команда. Если вы обращаетесь к командам относительным способом, время поиска файлов представляет собой большие накладные расходы. Когда вы вызываете lc, интерпретатор shell ищет эту команду, затем lc вызывает ls, которую тоже нужно найти. Если после этого результаты пропускаются через more или pr, то требуется дополнительный поиск. А полные маршрутные имена распознаются интерпретатором shell сразу же (он видит, что первым символом является /), и нужная команда может быть вызвана быстро. Издержки на поиск - единственные издержки команды lc.
Использование полных имен, естественно, требует, чтобы вы знали, где в системе размещены утилиты, к которым вы хотите обратиться. Вы можете применить команду paths, чтобы получить корректные полные имена для жесткого указания их в тексте вашего командного файла, а можете переписать данный командный файл при переходе в другую систему. Это просто еще одна иллюстрация универсального компромисса между скоростью и эффективностью, с одной стороны, и гибкостью и мобильностью, с другой.

2.2.2. ll - вывод файловой информации в длинном формате

-------------------------------------------------------------
ИМЯ: ll
-------------------------------------------------------------

ll Выдает список файлов в длинном формате

НАЗНАЧЕНИЕ

Выдает список файлов в длинном формате (-l). Распечатку можно пропустить через команду more.

ФОРМАТ ВЫЗОВА

ll [-m] [ls options] file [file...]

ПРИМЕР ВЫЗОВА

ll *.c
Выдача списка файлов с исходными текстами на языке Си в длинном формате.

ТЕКСТ ПРОГРАММЫ


1  :
2  # @(#) ll v1.0   Long listing of files   Author: Russ Sage
2а                    Выводит список файлов в длинном формате
3
4  if [ "$1" = "-m" ]
5    then MORE="| /usr/bin/more"
6          shift
7    else MORE=""
8  fi
9
10 eval /bin/ls -al $@ MORE

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ОПИСАНИЕ

ЗАЧЕМ НАМ НУЖЕН КОМАНДНЫЙ ФАЙЛ ll?

Мотивы для создания команды ll те же, что мы обсудили ранее для команды lc. Мы можем использовать ll для нескольких целей. Она уменьшает количество требуемых нажатий на клавиши, позволяет избежать необходимости помнить специальные опции и вообще настраивает систему соответственно нашим требованиям вместо того чтобы нам приспосабливаться к системе.

ЧТО ДЕЛАЕТ ll?

Основой этой команды является известная команда "ls -l". Она, если вы помните, дает очень емкую информацию о каждом файле, включая права доступа, связи, имя владельца, размер и так далее. (Кстати, программисты, использующие язык Си, могут получить эту информацию при помощи системного вызова stat(2).) Поскольку такой список при наличии множества файлов может легко переполнить экран, то предоставляется опция -m. Она обеспечивает постраничный вывод с помощью команды more. Отметим, что если используется эта опция, то она должна стоять первой. Строка символов, соответствующая этой опции, удаляется командой shift, так что она не смешивается с именами файлов и обычными опциями команды ls, которые передаются как аргументы.
После опции -m (если она есть) ll допускает указание любых других допустимых опций команды ls. Можно также использовать любую комбинацию имен файлов. Здесь применимы обычные средства порождения имен файлов: * соответствует любым символам, ? - одному символу, а символы [] задают диапазон из некоторого набора символов. В итоге мы получили команду ls, которая по умолчанию работает как "ls -l", вызывает команду more с помощью одной опции вместо необходимости указания конвейера в командной строке и при этом сохраняет гибкость команды ls.

ПРИМЕРЫ

ПОЯСНЕНИЯ

Если первым позиционным параметром является -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, но ваши выходные данные по-прежнему были бы выведены в правильном формате.

2.2.3. kind - вывод однотипных файлов

-------------------------------------------------------------
ИМЯ: kind
-------------------------------------------------------------

kind Выдача списка имен файлов определенного вида

НАЗНАЧЕНИЕ

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

ФОРМАТ ВЫЗОВА

kind [-a] [-d] [-t] [-x] [file...]

ПРИМЕР ВЫЗОВА

more `kind /etc/*`
Вывод командой more всех текстовых файлов, имеющихся в каталоге /etc.

ТЕКСТ ПРОГРАММЫ


1   :
2   # @(#) kind v1.0   Prints files of the same kind  Author: Russ Sage
2а                       Выводит список файлов определенного вида
3
4   if [ $# -gt 0 ]
5     then if [ `echo $1 | cut -c1` = "-" ]
6            then case #1 in
7                 -a)   KIND='archive'
8                       shift;;
9                 -d)   KIND='data'
10                      shift;;
11                -t)   KIND='text'
12                      shift;;
13                -x)   KIND='executable'
14                      shift;;
15                 *)   echo "kind: arg error"                           >&2
16                      echo "usage: kind [-a] [-d] [-t] [-x] [file...]" >&2
17                      echo "       -a  archive"                        >&2
18                      echo "       -d  data"                           >&2
19                      echo "       -t  text, default"                  >&2
20                      echo "       -x  executable"                     >&2
21                      echo "       if no args, reads stdin"            >&2
22                      exit 1;;
23                esac
24         fi
25   fi
26
27  : ${KIND:='text'}
28
29  case $# in
30  0)   while read FILE
31       do
32           file $FILE | fgrep $KIND | cut -d: -f1
33       done;;
34  *)   file $@ | fgrep $KIND | cut -d: -f1;;
35  esac

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ОПИСАНИЕ

ЗАЧЕМ НУЖЕН КОМАНДНЫЙ ФАЙЛ kind?

Файловая система UNIX имеет строгие стандарты при рассмотрении файлов. Имеется три вида файлов: обычные файлы (тексты, данные, исполняемые файлы), каталоги и устройства. Каждый вид файлов имеет свое предназначение и обычно имеет особые команды или файлы данных, которые работают с ним.
Давайте рассмотрим, как некоторые из существующих команд системы UNIX рассматривают типы файлов. Команда ls делает различие между каталогами и другими файлами, поэтому она может быть полезна. Другой важной командой является команда file. Она сообщает вам, какой тип имеет данный файл, что потенциально полезно, но слишком мало. Для того чтобы извлечь из команды file какую-либо полезную информацию, вы должны надстроить над ней некоторую программу. Что нам действительно нужно, так это некий гибрид команд ls и file, т.е. утилита, которая выводит имена всех файлов указанного типа.
Примером полезности такого рода утилиты может служить анализ содержимого каталогов. Возьмем, например, каталог /etc. Он содержит программы, файлы данных и текстовые файлы. Для каждого из этих типов требуется свой собственный тип анализа. Программы анализируются командами ls, size, nm и file. Файлы данных анализируются командой od. Текстовые файлы анализируются командами more, wc, head, tail и другими. Таким образом, обычно вам необходимо работать в данный момент времени с файлами какого-нибудь одного типа.

ЧТО ДЕЛАЕТ kind?

Командный файл kind - это утилита, которая распечатывает имена всех файлов, имеющих указанный тип. Она имеет интерфейс, похожий на интерфейс команды ls, т.е. вы можете передать ей опции и имена файлов или символы расширения имен файлов. При выводе отображаются полные имена, если они были указаны, но вы можете избежать появления лишней информации при выводе, если предварительно перейдете в нужный каталог с помощью команды cd. Например, если бы я находился в моем регистрационном каталоге (/usr/russ) и ввел команду
$ kind -d /etc/* то вывод мог бы выглядеть так:

--------------------------------
|
|   /etc/mnttab
|   /etc/utmp
|   /etc/wtmp
|
То есть, вывелся список всех файлов данных. А если бы я выполнил такую последовательность команд:
$ cd /etc
$ kind -d *

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

-----------------------------------------------
|
|    mnttab
|    utmp
|    wtmp
|
Затем выход в таком виде может быть использован во внешней команде для распечатки и анализа файловой информации.
Допустимыми опциями команды kind являются -a для файлов архивов, -d для файлов данных, -t для текстовых файлов (что является умолчанием) и -x для исполняемых файлов. Определение этих типов соответствует команде UNIX file. Заметим, что критерии того, что файл является исполняемым, в команде file отличаются от тех, которые применяет команда ls: ls проверяет биты x в индексном дескрипторе файла, в то время как file проверяет, являются ли первые несколько байтов содержимого файла "магическим числом". Это магическое число является идентификатором структуры a.out (см. /usr/include/a.out.h), который сообщает "Я являюсь скомпилированной Си-программой".
Имена файлов появляются в командной строке после опций. Эти имена могут быть порождены любым стандартным методом системы UNIX. Если в командной строке нет имен файлов, то kind превращается в фильтр и читает стандартный ввод для получения списка имен файлов. (Обратите внимание, что я сказал "имен файлов", а не "файлов". Можно использовать опции, поскольку они убираются из командной строки командой shift по мере того, как они встречаются.) Таким образом, вы можете использовать другие команды для того, чтобы передать по конвейеру список файлов утилите kind. Она отфильтровывает и выводит только те из них, которые соответствуют нужному вам типу.

ПРИМЕРЫ

ПОЯСНЕНИЯ

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

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

Было бы неплохо, если бы командный файл kind мог работать одновременно с разными типами файлов. Это означает наличие несколько опций в командной строке, например -a и -d. Вам могла бы понадобиться составная строка, в которой каждая часть была бы отделена символом |. Затем эта строка могла бы быть использована в команде egrep, например, "egrep 'archive|data'". Вам пришлось бы организовать цикл по командной строке вместо использования фиксированных позиций и убедиться в том, что вы не получите зациклившийся конвейер, когда задана только одна опция.

2.2.4. m - простой доступ к команде more

-------------------------------------------------------------
ИМЯ: m
-------------------------------------------------------------

m Простой доступ к команде more

НАЗНАЧЕНИЕ

Обеспечивает быстрый и простой способ постраничного вывода

ФОРМАТ ВЫЗОВА

m [more options] [file ...]

ПРИМЕР ВЫЗОВА

m * Вывод командой more всех файлов текущего каталога

ТЕКСТ ПРОГРАММЫ


1   :
2   # @(#)  m v1.0    Easy access to more
2а                    Простой доступ к команде more
3
4   /usr/bin/more $@

ОПИСАНИЕ

ЗАЧЕМ НУЖЕН КОМАНДНЫЙ ФАЙЛ m?

Система UNIX сильно загромождается по мере своего функционирования. В ней обычно имеется множество текстов и данных. Просмотр громадного количества данных требует многократного нажатия на клавиши, если вы должны вручную управлять постраничным выводом или периодически вызывать команду more. Нам необходимы программные средства, которые помогут нам ускорить эту работу. Одним из таких средств является m. Оно очень короткое и простое, но это не значит, что оно бесполезно.
Имеется два основных способа вывода данных на экран. Первый способ - непосредственный вызов команды, например, "more datafile". Вы направляете данные на экран самой командой. Второй способ - использовать какую-нибудь команду для получения данных, а затем в конце перехватить их командой more, например "od -c. | more". В обоих этих случаях мы вводим с клавиатуры много символов. Сделав так, чтобы команда more вызывалась по одному символу, мы могли бы уменьшить последние две команды на шесть нажатий на клавиши. За целый день это хоть немного предохранит клавиатуру от разрушения! (Если ваша система поддерживает вызов команд по псевдонимам (aliasing), то, как указывалось ранее, вы могли бы использовать в этом случае команду alias: "alias m more".)

ЧТО ДЕЛАЕТ m?

Надеемся, все ваши системы имеют команду more или хотя бы ее замену. Постраничный вывод имеет важное значение при работе с текстом большого объема.
Все опции и аргументы передаются в командной строке. Вы можете указать опции команды more в командной строке команды m. Они передаются без изменений. Можно указать имена файлов. Если они указаны, команда more выводит их. В противном случае ожидается поступление данных со стандартного ввода. Таким образом, m может быть использована в качестве "перехватчика" или как фильтр, как и команда more.
Для тех, кто не слишком знаком с опциями команды more, отметим, что существуют две изящные возможности: 1) вход в редактор vi в том месте, где находится курсор при выводе командой more; 2) выход из more для запуска команды shell и возврат в то место, откуда вы вышли. Первая опция выполняется при нажатии клавиши "v" в строке состояния команды more. (То есть когда more отобразила полный экран текста и ждет продолжения.) Вторая опция запускается при вводе ":!cmd" или "!cmd". Когда команда выполнится, more вернется в то же место. Как видите, это синтаксис командной строки ex. Команда more в самом деле имеет небольшую часть редактора ex, спрятанную внутри нее. Вы можете выполнить многие команды редактора, указывая их после подсказки в строке состояния команды more.
Обычный сеанс работы выглядит так:

--------------------------
|
|    m `path termcap`            <-поиск таблицы описания тер-
|                                  минала (termcap) и вывод
|          .                       ее командой more
           .
           .
  --More--(5%)                   <-строка состояния more
  v                                vi /etc/termcap
  vi +210 /etc/termcap           <-командная строка для редак-
                                   тора vi получена от more
           .
           .
  :q                             <-выход из vi
  --More--(5%)                   <-возврат в more
  :!sh                             порождение нового shell'а
  $ date                           запуск команды date
  Wed Apr 23 07:15:04 PST 1986
  $ ^d                           <-убрать порожденный shell
  --More--(5%)                   <-возврат в more
  :f                               распечатка имени файла,
                                   выводимого командой more
  "/etc/termcap" line 54           выход команды f
  --More--(5%)
  f                              <-команда more для пропуска
                                   полного экрана
   .skipping 23 lines
           .
           .
  --More--(9%)                   <-пропуск и выдача текста
  q                                выход из команды more

ПРИМЕРЫ

ПОЯСНЕНИЯ

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

2.2.5. mmm - обработка программой nroff макрокоманд для рукописей

--------------------------------------------------------------
ИМЯ: mmm
--------------------------------------------------------------

mmm Командная строка nroff для макросов обработки рукописей

НАЗНАЧЕНИЕ

Вызывает текстовый процессор nroff со специальными опциями, которые инициализируют макросы обработки рукописей.

ФОРМАТ ВЫЗОВА

mmm file [...]

ПРИМЕР ВЫЗОВА

mmm memo
Обработать с помощью nroff файл моих заметок memo и отобразить его на экран

ТЕКСТ ПРОГРАММЫ


1   :
2   # @(#)  mmm v1.0 Nroff command line with mm macros Author: Russ Sage
2а                   Командная строка nroff с макросами mm
3
4   if [ "$#" -eq 0 ]
5     then echo "mmm: wrong arg count"   >&2
6          echo "usage: mmm file [...]"  >&2
7          exit 1
8   fi
9
10  LIST=""
11  for ARG in $*
12  do
13          if [ ! -f $ARG ]
14            then echo "mmm: $ARG is not a regular file" >&2
15            else LIST="$LIST $ARG"
16          fi
17  done
18
19  nroff -r0O -mm $LIST

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ОПИСАНИЕ

ЗАЧЕМ НУЖЕН КОМАНДНЫЙ ФАЙЛ mmm?

Одним из фактов делового мира является работа с бумагами. Мы производим заметки, письма, контракты, документы, руководства и так далее. Если вы знакомы со стилем производства документации в системе UNIX, то ваши текстовые файлы в основном представлены в одном из форматов программы nroff.
Однако различные программы форматирования текстов служат различным целям. Имеется стандартный nroff и nroffс дополнениями, такими как макросы ms и mm. Для подготовки графической информации и выполнения типографских работ разработана программа troff. Система AT&T имеет целую программную среду под названием Writers Workbench, и система Berkeley имеет аналогичные возможности.
Большинство наших задач по написанию каких-либо текстов может быть сведено к нескольким стандартным форматам, таким как письма, рукописи вообще, страницы руководств и так далее. Не так легко запомнить опции команды nroff (или другой команды), которые следует использовать в данном случае, да мы и не должны делать это. Наша команда mmm служит иллюстрацией программы, которую мы можем запускать всякий раз, когда нам нужен определенный формат. Вы можете создать несколько версий программы, которые удовлетворяют вашим собственным нуждам при написании текстов.
Использование заготовленных заранее команд означает, что мы можем делать полезную работу, даже если некоторое время мы не выполняли работу определенного вида. Мы также можем избежать многократных нажатий на клавиши. Мастера UNIX'а периодически уединяются в своих горных убежищах, где штудируют справочные руководства в поисках полезных, но доселе незамеченных опций, которые могут быть встроены в программные средства для повседневной работы. Если слишком некритично полагаться на ваш текущий набор инструментальных средств, то можно пропустить полезные возможности.

ЧТО ДЕЛАЕТ mmm?

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

ПРИМЕРЫ

ПОЯСНЕНИЯ

В строке 4 проверяется, равно ли нулю количество аргументов в командной строке. Если да, в стандартный файл ошибок выдается сообщение об ошибке. Выводится также синтаксическая подсказка, и mmm завершается с плохим статусом.
Переменная LIST инициализируется нулевым значением в строке 10. Обычно переменные интерпретатора shell и так в начале равны нулю, но предварительная установка значения является хорошим стилем программирования.
Затем мы обрабатываем каждый аргумент командной строки в цикле (строки 11-17). Все аргументы должны быть именами файлов, поэтому каждый из них проверяется на то, существует ли он как обычный файл. Если это не файл, то в стандартный файл ошибок выводится сообщение об ошибке. Тем не менее программа не завершается. Не следует аварийно прекращать всю программу только потому, что нет указанного файла. Мы пропускаем его и идем до конца списка аргументов. Это особенно актуально, если данная команда используется как фоновая во время выполнения другой работы. Пользователь скорее согласится с тем, чтобы было выполнено побольше работы, чем не сделано вообще ничего. Это решение, принятое в данной программе, и вы можете изменить его, если оно не подходит в вашей ситуации.
Если имени соответствует допустимый файл, оно добавляется в список хороших имен файлов. Этот список становится главным списком для команды nroff.
После того как все аргументы проверены, мы в строке 9 строим и выполняем командную строку nroff.
Опция -rO0 для nroff указывает макросам обработки рукописей (пакету mm) установить регистр, который имеет дело с отступом текста, в состояние, соответствующее отступу в 0 символов. Это значит, что весь текст начинается с крайней левой позиции, т.е. выровнен слева. Путем проведения экспериментов я обнаружил, что левое выравнивание текста программой nroff и установка отступа для принтера дает наиболее надежный вывод на печать. В противном случае, если вы установите отступ текста в nroff и отступ в принтере, то может произойти настоящее столкновение, когда дело коснется вывода колонок в странице. Вы можете изменить это место, если ваши программы вывода или устройства печати ведут себя как-то иначе. Опция -mm указывает программе nroff просмотреть библиотеку макросов обработки рукописей, чтобы определить, используются ли какие-либо из них во входном документе. Эти макросы очень большие и требуют много времени центрального процессора. Если вам необходимо использовать их, то вам потребуется большой компьютер или компьютер, специально предназначенный для этой цели, чтобы добиться хорошего времени получения результата.
Последним аргументом является $LIST. В этой переменной находится строка имен файлов, разделенных пробелами. Эти имена помещаются в командную строку nroff. Можете быть уверенными, что в этом месте нет никаких ошибок.

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

Поскольку все аргументы рассматриваются как имена файлов, то у нас нет способа передачи дополнительных команд пакету mm. Наличие такой возможности было бы желательным, поскольку при экспериментировании с командой nroff вам необходимо пробовать различные опции, чтобы увидеть, как они действуют. Было бы тяжелой работой выполнять редактирование текста mmm, чтобы добавить одноразовые опции, которые могут вам никогда не понадобиться или опции, которые вы должны постоянно менять.
Один из путей достижения большей гибкости - посмотреть, имеет ли какой-либо аргумент дефис в качестве первого символа. Если да, перехватить эту опцию и убрать ее из списка имен файлов. После этого вы бы имели список опций, которые нужно включить в командную строку, и список имен файлов, подлежащих обработке.
Отметим, что место, занятое в нашем командном файле указанием пакета mm, можно вместо этого заполнить ссылкой на другие макропакеты, имеющиеся в вашей системе, например -ms или -me, в зависимости от нужного вам формата. Отказ от поиска макросов, которые вам не нужны, ускорит обработку: подробности вы найдете в документации по nroff или troff.

2.2.6. pall - печать всех файлов в дереве

-------------------------------------------------------------
ИМЯ: pall
--------------------------------------------------------------

pall Распечатка всех файлов в дереве каталогов

НАЗНАЧЕНИЕ

Находит все файлы в заданном каталоге в соответствии с некоторым критерием, разбивает файлы на страницы и помещает результат в один файл, готовый к распечатке на принтере.

ФОРМАТ ВЫЗОВА

pall [-t|-d] directory

ПРИМЕР ВЫЗОВА

pall /usr/lib
Выводит на печать постранично все текстовые файлы в каталоге /usr/lib

ТЕКСТ ПРОГРАММЫ


1.  :
2   # @(#) pall v1.0 Print all files in a tree Author: Russ Sage
2а                   Печатает все файлы в дереве
3
4   if [ $# -eq 0 -o $# -gt 2 ]
5     then echo "pall: wrong argument count"  >&2
6          echo "usage: pall [-t|-d] dir"     >&2
7          echo "  -t text (default)"         >&2
8          echo "  -d dev (.c,.h,.mk,.s)"     >&2
9          exit 1
10  fi
11
12  NAME=""
13  if [ `echo $1 | cut -c1` = "-" ]
14   then case $1 in
15        -t)     NAME=""
16                shift;;
17        -d)     NAME="-name \"*.[chms]*\""
18                shift;;
19         *)     echo "pall: invalid arg $1"    >&2
20                echo "usage: pall [-t|-d] dir" >&2
21                echo " -t text (default)"      >&2
22                echo " -d dev (.c,.h,.mk,.s)"  >&2
23                exit 1;;
24        esac
25  fi
26
27  echo "creating output file: /tmp/lpr$$"
28
29  eval find $1 -type f $NAME -print | sort | while read FILE
30  do
31          if file $FILE |
32            egrep 'exec|data|empty|reloc|cannot open' >/dev/null 2>&1
33          then continue
34          else file $FILE > /dev/tty
35               pr $FILE
36          fi
37  done >> /tmp/lpr$$
38
39  echo "\nSend /tmp/lpr$$ to line printer (y/n): \c"
40  read CMD
41  if [ "$CMD" = "y" ]
42    then lpr /tmp/lpr$$
43  fi

ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ

ОПИСАНИЕ

ЗАЧЕМ НУЖЕН КОМАНДНЫЙ ФАЙЛ pall?

Эта утилита объединяет концепции обхода дерева файлов и вывода содержимого файлов. Даже когда файлы упрятаны в подкаталогах, мы все равно хотим найти их. Нам необходима для этого утилита, которая обходит древовидную структуру файлов, находит файлы нужного нам типа, готовит их к распечатке и ставит в очередь для вывода на принтер.
Такого рода утилита особенно полезна, если исходные тексты или файлы с документацией хранятся в иерархическом дереве. Дело осложняется тем, что обычно эти текстовые файлы смешаны с исполняемыми файлами (откомпилированными программами), файлами данных и, возможно, с архивными файлами. Необходимо иметь возможность отфильтровать все непригодные для печати файлы и подготовить текстовые файлы. Должны быть проверены все файлы с тем, чтобы ни один не был пропущен.
Для выполнения всего этого процесса вручную требуется, чтобы вы по команде cd переходили в каждый уровень дерева файлов, находили текстовые файлы, обрабатывали их (обычно командой pr системы UNIX или каким-либо другим текстовым процессором) и распечатывали их. После выполнения всей этой работы вы еще должны собрать все отдельные распечатки вместе в строго определенном порядке. Это большая работа, подверженная ошибкам со стороны человека. Почему бы не позволить машине выполнять эту работу? Сейчас мы имеем концепции и средства, необходимые для создания такой утилиты.
В дополнение к возможности управления файлами, pall также может управлять устройством печати. Обычно каждое задание, поставленное в очередь на выполнение к принтеру, имеет заголовок, который печатается первым. Это значит, что если вы поставили в очередь на печать десять отдельных заданий, то впереди каждого из них будет две-три страницы, которые должны быть убраны вручную. Помножьте это на сотни файлов, и вы будете иметь кучу бумаг, которые должны быть выброшены.
Pall исключает эти потери, поскольку все обработанные данные собираются в один большой текстовый файл. Когда вся обработка выполнена, этот один файл может быть поставлен в очередь на печать или сохранен для некоторых других целей. Единственное ограничение при таком подходе заключается в максимальном размере файла, который вы можете создать. Этот размер вычисляется умножением значения ulimit на размер блока. Например, мое значение ulimit равно 4096. Размер блока в данном случае равен 512, а не 1024. Максимальный размер файла равен 2097152. Вы можете вычислить это прямо с клавиатуры, как показано ниже:
$ ulimit
4096
$ expr 4096 \* 512
2097152

Этого значения достаточно для большинства случаев.

ЧТО ДЕЛАЕТ pall?

Командный файл pall предназначен для поиска указанных файлов, обработки их командой UNIX pr и сборки всех результатов в один файл. После того как все исходные файлы будут обработаны командой pr, вам будет задан вопрос о том, хотите ли вы поставить выводной файл в очередь на печать к принтеру. Результирующий файл сохраняется в каталоге /tmp, где его можно использовать для других целей или удалить.
Опциями pall являются -t и -d. Опция -t используется по умолчанию и нет необходимости ее указывать. Она предназначена для документации и указана в командной строке, чтобы более ясно показать действие, которое будет выполнено.
Если выбрана текстовая опция, ищутся все файлы в древовидной структуре и затем отбираются только текстовые файлы. Если указана опция разработки -d (development), то ищутся только файлы, связанные с разработкой программ. Затем эти файлы отфильтровываются с целью получения текстовых файлов. Считается, что к разработке программ относятся файлы, имена которых имеют вид *.c для исходных файлов на языке Си, *.h для включаемых файлов заголовков, *.mk для файлов построения программ (makefiles) и *.s для исходных файлов на ассемблере. Если требуются какие-либо другие шаблоны, то такие символы могут быть легко помещены в текст программы.
Прежде чем начнет выполняться поиск файлов, на экран выводится имя временного файла, чтобы вы знали, как обратиться к нему после завершения выполнения команды. Все результаты, полученные после обработки файлов, направляются в этот один файл. Командный файл pall также выводит на экран сообщения, когда он обрабатывает файлы. Вывод файлов выполняется в реальном времени по мере обработки файлов. Распечатка производится при помощи обычной команды UNIX'а file. По распечатке вы можете судить о том, файлы какого типа обрабатываются в данный момент. Если какой-либо норовистый файл проскользнет, то вы знаете, где он размещен и какого он типа. Это делает отладку гораздо более простой.
Для файлов выполняется обработка, которая является действием по умолчанию команды pr. Она разбивает файл на страницы и ставит заголовок в начале каждой страницы. Заголовок содержит дату, имя файла и номер страницы. Нет никакого иного способа передать заголовок командному файлу pall во время его работы, поскольку он предполагает, что вы хотите знать имя каждого файла таким, как оно есть на диске. Изменение строки заголовка вызвало бы только неприятности и дополнительную работу.
Способ вызова pall влияет на формат имени файла в заголовке. Если вы вызвали pall, используя абсолютное имя каталога, то в распечатке используются полные маршрутные имена. Если вы вызвали pall с относительными маршрутными именами, то они и используются при выводе на печать. Внутри pall используется команда find системы UNIX. Команда find использует данные из командной строки, т.е. те, которые ввел пользователь. Выводимый заголовок изменяется в зависимости от того, что указано в командной строке, которую использует find. Если вы вызываете pall, используя следующую командную строку, то заголовок содержит полное маршрутное имя:

-------------------------------------------
|
|    $ pall /usr/include
|
|    May 5 10:39 1986 /usr/include/a.out.h Page 1
|              .
|              .
|              .
|    May 5 10:39 1986 /usr/include/ar.h Page 1
|              .
|              .
|              .
Если вы вызываете pall с помощью относительной нотации, то имена файлов также являются относительными, что не очень хорошо. Если у вас есть несколько каталогов, в которых имеются одинаковые имена файлов, то вы не сможете быть уверены, что смотрите на правильную распечатку. Вот как это может выглядеть:

---------------------
|
|    $ cd /usr/include
|    $ pall .
|
|    May 5 10:39 1986 ./a.out.h Page 1
|              .
|              .
|              .
     May 5 10:39 1986 ./ar.h Page 1
               .
               .
               .

ПРИМЕРЫ

ПОЯСНЕНИЯ

В самом начале производится проверка на наличие ошибок в командной строке. Если в ней нет аргументов или их больше двух, выводится сообщение об ошибке, синтаксическая подсказка и программа завершается с неудачным статусом возврата.
В строке 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. Когда файлы найдены, их имена выводятся в стандартный вывод. Стандартный вывод по конвейеру передается команде sort, располагающей имена файлов по порядку. Это сильно помогает при сортировке горы выводной информации. Чтение кипы распечаток толщиной в один фут может свести с ума кого угодно.
Отсортированные имена по конвейеру передаются в цикл while, который читает имена файлов по одному. Обратите внимание, что стандартный вывод для всего цикла while переадресовывается во временный файл, что облегчает сборку всех выходных результатов в одном месте вместо переадресации каждого вызова команды в файл.
Для каждого подходящего файла выполняется проверка в строках 31-36. Проверка начинается с запуска команды file. Выход file по конвейеру передается команде egrep, которая ищет тип файла, соответствующий набору нескольких выражений. Если какое-либо выражение подходит, то нам не нужно обрабатывать этот файл. Это не текстовый файл, и его нельзя вывести на принтер. Во многих случаях файлы данных содержат большое количество символов прогона формата, которые выталкивают страницу после каждой пары символов. Если вы не будете находиться рядом с принтером, когда печатаются такие файлы, то вы можете получить листинг, занимающий половину ящика бумаги, затратив целый лес на ненужную работу.
Нам не нужен выход команды egrep, а только ее статус возврата. Если egrep обнаруживает одно из указанных ей выражений, она завершается со статусом успеха, или 0. Тем самым проверка if включает выполнение оператора then, который в данном случае выводит нас из конструкции if-then-else и продолжает цикл while, пропуская таким образом файл.
Если же egrep не обнаружила ни одну из указанных символьных строк, то выполнение продолжается с оператора else, который выполняет еще одну команду file и переадресовывает ее вывод на устройство с именем /dev/tty. Это универсальное имя устройства, которое гарантирует вам вывод на экран вашего терминала. UNIX обеспечивает, что указание /dev/tty обходит любые команды переадресации вывода, действующие в данный момент. Поскольку стандартный вывод уже переадресован для всего цикла while, то нам нужно попасть на устройство /dev/tty, чтобы вывод шел на экран терминала, а не в файл печати. Отображение на терминал имени обрабатываемого файла позволяет пользователю знать, какой файл будет добавлен к распечатке.
Если файл удовлетворяет нашим критериям, он обрабатывается командой pr. Результат направляется в стандартный вывод, который переадресован циклом while так, чтобы результат четко попадал в один файл. Отметим, что нам нужно поставить символ добавления в файл вывода (>>). В противном случае мы получим запись на место существующего файла печати, а значит в файле печати будет находиться только последний обработанный файл.
После того как все файлы обработаны, задается вопрос о том, хотите ли вы вывести результирующий файл на печать. Предлагается ответить на этот вопрос "да" (yes) или "нет" (no), однако в программе проверяется только положительный ответ (yes). Это значит, что нажатие любой клавиши трактуется как ответ "no", кроме клавиши "y", означающей "да". Ответ пользователя читается с клавиатуры, и проверяется, является ли он символом "y". Если да, то файл ставится в очередь на печать. Если нет, дальнейшая проверка не производится и командный файл завершается.
Отметим, что запрос о выводе на печать поступил в стандартный вывод. Переадресация вывода действовала только во время работы цикла while, а затем прекратилась. Так было сделано потому, что цикл while был фактически еще одним порожденным shell-процессом (subshell) и переадресация действовала только в этом те внимание, что маршрутнаяустановите значения переменных вне цикла, а затем измените их внутри цикла. После завершения цикла переменные по-прежнему имеют свои первоначальные значения, а не измененные в цикле значения. Измененные значения касались переменных порожденного интерпретатора shell, которые исчезли, когда порожденный shell завершился. Переменные интерпретатора shell могут передавать значения только вниз, порожденным процессам, но процессы-потомки не могут передавать значения переменных вверх, родительскому процессу. Обычно передача значений переменных поддерживается при помощи какого-либо файла, в котором хранятся данные для обмена между родительским процессом и процессом-потомком.

УПРАВЛЕНИЕ ВЫВОДНЫМИ ФАЙЛАМИ БОЛЬШИХ РАЗМЕРОВ

Как мы уже отмечали, общий размер выводного файла ограничен. Напомним, что команда find проходит все дерево каталогов вниз до конца по всем поддеревьям, начиная с каталога, имя которого указано в командной строке. Если вы находитесь на вершине очень глубокого дерева, то обрабатываться могут буквально сотни файлов. Поскольку вы ограничены максимальным размером выводного файла, вы можете обработать только ограниченное число файлов. Конечно, количество файлов, которое вы можете обработать, зависит также от того, насколько велики входные файлы.
Если выводной файл достигает своего максимума, все добавляемые после этого данные теряются. Потеря данных весьма болезненна, и обычно требуется некоторое время, чтобы ее обнаружить. В медленно работающей системе попытка обработать большое дерево, например все исходные тексты системы UNIX, может занять целый час и даже больше, прежде чем выходной файл заполнится. Это означает, что вы должны находиться рядом и следить за тем, когда файл переполнится. Если он все-таки переполнился, вы должны все выбросить и начать сначала. Это также означает, что вы должны перейти вниз по дереву. Это может быть проблемой в сбалансированных деревьях.
Например, рассмотрим каталог /usr/lib. Этот каталог содержит много файлов на первом уровне и много каталогов первого уровня. Если бы мы не обработали все файлы каталога /usr/lib за одну попытку, мы должны были бы пойти вниз по подкаталогам каталога /usr/lib. Попытки делать это вручную и запускать pall в каждом подкаталоге заняли бы много времени и могли бы привести к ошибкам с вашей стороны. Кроме того, pall допускает указание только одного имени каталога, что приведет к получению большого количества распечаток и к путанице при их сортировке.
Что же делать? Радикальным решением является увеличение значения ulimit. Вы можете сделать это либо с помощью программы на языке Си, использующей системный вызов ulimit, либо командой shell'а ulimit. Техника выполнения такой работы представлена в главе 7.

ВОЗМОЖНЫЕ МОДИФИКАЦИИ

Возможно, вы захотите добавить свои собственные штрихи в некоторых местах командного файла. Первым местом является то, где указываются символы поиска файлов программной разработки. Символы, использованные нами - это наиболее употребимые суффиксы в UNIX. Если вы используете не Си и ассемблер, а другие языки, то вы можете добавить соответствующие символы.
Следующим местом, где могут быть сделаны дополнения, являются опции, которые может понимать pall. Вам могут понадобиться файлы с определенными именами или определенными типами, например, файлы nroff. Эти опции могут быть легко добавлены в оператор case, что улучшит команду.
Последним местом возможных изменений является тип файлов, которые нужно пропускать. Символьная строка для команды egrep покрывает большинство важных нетекстовых типов файлов. В вашей системе могут быть какие-то особые типы или же имена могут быть другими. Если вам необходимо дополнить строку, сделайте это. Команда egrep может обработать довольно много информации. Я не знаю ее ограничений. Возможно, вы обнаружите их, просматривая исходный текст утилиты egrep. Если получается слишком длинная строка и не помещается на экране, ничего страшного. Перенос на следующие строки экрана не опасен, пока общее количество символов не превысит 255. Опасно только указывать переадресацию символьной строки if на нулевое устройство в следующей после команды egrep строке. Кажется, что все работает правильно, но это не так. Переадресация должна указываться в той же строке, где стоит команда egrep.
В данной главе сделано очень много. Наиболее важно то, что получено множество новых идей, которые можно использовать при эксплуатации программной среды и просмотре файлов любого типа. В следующей главе мы углубимся в рутинную работу по ежедневному сопровождению файлов и используем изученные средства, чтобы они облегчили нашу жизнь.