нием.) Пример вида экрана приведен ниже. --------------------------------------- | Среда, май 28 13:18:49 | | Cpio - Сохранение/восстановление файлов | --------------------- | Копирование данных | Восстановление данных | Список файлов на носителе | Полный список файлов на носителе | <ВК> для выхода | | Нажмите b,r,f,l, или <ВК>: В левом верхнем углу расположен день недели, месяц, день месяца. Это поля 1, 2 и 3 команды date. В правом верхнем углу расположено теку- щее время. Это поле 4 команды date. Все эти данные приводятся для того, чтобы меню на экране смотрелось красиво, было равномерно заполнено и информативно. После того, как меню выдано на экран, строка 27 читает команду пользователя. Заметим, что один из ключей вызывает завершение програм- мы, если был нажат только возврат каретки. Каким образом мы проверяем это? Мы заключаем в кавычки входную переменную таким образом, что про- верка распознает нулевое значение (см. строки 28-30). Если был введен ноль, мы выходим из текущего цикла while. Тем самым мы попадаем в конец программы, которая после этого завершает выполнение. Если входное зна- чение не было равно нулю, мы продолжаем и выполняем в следующей команде проверку на наличие ошибки. В строке 32 проводится инициализация переменной ABORT путем сбрасывания ее. Это будет детально пояснено позже. А сейчас мы только скажем, что эта переменная существует, поскольку имеется конфликт между тем, как выполняется команда break, и структурой данной программы (т.е. полностью управляемой с помощью меню утилиты). В строках 34-65 разместился вложенный цикл while, который обраба- тывает подменю. Причина, по которой мы использовали циклы типа "вечный while" заключается в том, что они выполняются, пока не получат нужное входное значение. Как только получены правильные входные данные, мы вы- ходим из цикла. Это очень простой способ обработки меню. В строках 36-45 мы снова используем оператор echo для выдачи на экран полного подменю. Это меню запрашивает имя устройства, которое используется в командах копирования/восстановления. Строка 47 читает входные данные от пользователя в переменную MEDIA. Значение переменной MEDIA затем оценивается в операторе case. Обратите внимание, что шаблоны сравнения включают символы и в верхнем, и в нижнем регистре. Это облегчает жизнь пользователя и делает немного более логичным программирование, уменьшая число проверок на ошибки, ко- торое мы должны произвести. Также заметьте, что каждый образец заканчи- вается оператором break. Когда обнаружено допустимое входное значение, мы желаем продолжать выполнение после конца оператора while, что осу- ществляется оператором break. Переменная DEV теперь установлена как маршрут к выбранному устройству. Ключ "a" в строках 55-60 требует дальнейшей обработки. Пользова- тель запрашивается об имени устройства, которое он выбрал. Если пользо- ватель забыл имя или решил не использовать этот ключ, он может ввести возврат каретки, который распознается как нуль и приводит к выполнению оператора continue. Это вызывает выполнение следующей итерации текущего цикла, которая снова выводит подменю. Еще один возврат каретки после этого может использоваться для выхода из подменю и возврата в главное меню. В противном случае, если пользователь ввел ненулевое значение, выполняется оператор break, и цикл меню завершается, имея маршрут, ука- занный в переменной DEV. Если пользователь ввел неверное значение, печатается сообщение об ошибке и подменю выводится снова. Заметьте, что ввод только возврата каретки в подменю устанавливает переменную ABORT в "on". Почему это так? Теперь мы подошли к той части, где язык командного процессора неп- рименим для нашего случая. Сценарий выглядит примерно так. Мы находимся в подменю. Мы решаем, что не будем здесь производить выбор, поэтому мы хотим выйти из подменю и вернуться в главное меню (или в предыдущее ме- ню). Если мы выйдем из цикла while подменю, мы попадем во внешний цикл while и продолжим обработку запросами о каталоге-источнике и катало- ге-приемнике. Если мы попытаемся решить эту проблему путем использования опера- тора "break 2", мы выйдем из обоих циклов while (попадая в самый низ программы) и программа завершится, ничего не сделав для нас. Снова не то, что мы хотим. Что мы действительно хотим, так это выйти из текущего (внутреннего) цикла и продолжить следующую итерацию во внешнем цикле для получения главного меню. Нет никакой возможности сказать командному процессору об этом, поэтому мы создали переменную в качестве флага для имитации этого действия и назвали ее ABORT. Если мы устанавливаем пере- менную ABORT в состояние "да", то мы НЕ желаем продолжать работу с ко- мандой главного меню, а хотим прекратить ее и вернуться в главное меню. На самом деле это означает продолжить, поэтому в строках 67-69 проверя- ется именно это. Если флаг ABORT установлен, подменю принудительно за- вершается и оператор continue заставляет снова печатать главное меню вместо того, чтобы пытаться выполнить какую-то наполовину определенную операцию копирования. В строке 71 мы получаем для проверки команду главного меню и ин- формацию, необходимую для ее обработки. Четырьмя основными командами являются копирование, восстановление, выдача списка файлов и выдача списка файлов с полной информацией. Если введена какая-то другая коман- да, выдается сообщение об ошибке и главное меню снова выводится на эк- ран. Единственный способ выхода из главного меню - это нажать возврат каретки без какого либо-текста перед ним и строка 28 позволит выйти из цикла. Команды копирования и восстановления используют относительное име- нование. Сначала они требуют указать каталог, затем переходят в этот каталог. Оператор echo и "холостое" чтение переменной CMD просят поль- зователя вставить дискету (или смонтировать магнитную ленту или еще что-нибудь) и нажать возврат каретки, когда все готово. В случае копирования для поиска ВСЕХ файлов, размещенных в дереве файлов, начиная с текущего каталога, используется команда find. Опера- тор find выдает отсортированный список файлов, поэтому файлы на носите- ле с копией отсортированы. Затем отсортированный список файлов переда- ется по каналу команде cpio с опциями -ocBv. Это означает: "потоковый вывод, использовать символьные заголовки, блоки размером по 5K, с выда- чей сообщений". При этом печатаются имена файлов по мере того, как они копируются на носитель. В случае операции восстановления используются ключи -icBvdmu. Это значит "потоковый ввод, использовать символьные заголовки, блоки по 5К, с выдачей сообщений для печати имен файлов по мере их восстановления, создавать каталоги при необходимости, файлы сохраняют исходную дату мо- дификации, и все файлы безусловно копируются". Если должен быть выдан только список файлов, то ключами будут или -icBt для печати таблицы скопированных файлов (это соответствует записи команды cpio "ls"), или -icBtv для печати таблицы файлов с более под- робной информацией ("ls -l" в записи для cpio). В конце выполнения каждой команды главного меню выдается сообщение hit (Нажмите <ВК>) Это сделано по той причине, что когда печатается главное меню, оно очищает экран. Если бы вы хотели получить список файлов, как описано выше, и не завершить работу, то напечатался бы список, выполнение достигло бы внешнего оператора "done", внешний цикл снова стартовал бы и экран очистился бы перед новой выдачей на него главного меню. Уф, на- конец появился список, даже до того как Эвелин Вуд с высшим образовани- ем смогла прочитать это! Для того, чтобы задержать очистку экрана, мы ожидаем нажатие на клавишу. Что бы ни было введено, оно читается в пе- ременную и снова никогда не используется. Это просто холостая перемен- ная. Замечания по операции копирования Вы можете производить копирование файлов многими путями, но давай- те рассмотрим некоторые отличия между командами cpio и tar. Первона- чально командой копирования в UNIX была команда tar. Эта утилита созда- ния копии на магнитной ленте была предназначена для ведения архивов на магнитной ленте и выполнения самого копирования. Она работает, но имеет некоторые особенности. Во-первых, каждый файл, помещаемый на ленту (или какой-либо носитель, который вы используете), выравнивается на границу килобайта. Это означает, что если ваш файл состоит из одного байта, ко- манда tar выделяет минимум 1К вашему файлу, что может привести к значи- тельной растрате пространства, если у вас много небольших файлов и вы копируете их на магнитную ленту. Однако команда tar имеет также ряд неплохих аспектов. Например, вы можете сказать ей, какой множитель блокировки вы используете и насколько велик образ копии, так что вы можете разбить большие копируемые потоки данных на много мелких частей (например k=360 для гибких дисков низкой плотности в системе XENIX). Одним из странных аспектов команды tar является то, что копия является одним длинным и непрерывным потоком, а при восстановлении используется уникальный фор- мат для каждого носителя. Например, вы должны, скажем, скопировать 10M данных. Вам лучше иметь достаточно отформатированных гибких дисков и приготовить их до того, как вы начнете копировать командой tar. Когда дискета заполнится, вы должны прервать выполнение команды и затем снова продолжить. Пример такой команды мог бы выглядеть так: cd $HOME tar cvefbk /dev/fd048ds9 18 360 . Здесь указано, что требуется скопировать ВСЕ файлы (рекурсивно об- ходя дерево сверху вниз) из текущего каталога (.) в файл на указанном устройстве, со множителем блокировки 18 K и размером образа копии 360 Кбайт. Одним из интересных аспектов здесь является то, что когда коман- да tar рекурсивно проходит вниз по дереву, она получает имена файлов в порядке расположения описатель файла, что обычно НЕ совпадает с отсор- тированным порядком. Вы НЕ МОЖЕТЕ получить отсортированный список фай- лов для копирования командой tar, если только не сделаете еще одну точ- ную копию всех данных, которые вы хотите скопировать и не разместите описатель файла в отсортированном порядке. Как это сделать? Вот так: cd $HOME find . -print | sort | cpio -pdv /bkpsort При этом получится новая копия всех ваших данных и имена файлов разместятся в отсортированном порядке. Если после этого вы перейдете в каталог /bkpsort и выполните команду tar, файлы будут перенесены на носитель в отсортированном порядке. Предположим, что для выполнения копирования требуется 15 дискет. При восстановлении этого полного набора вы должны вводить приведенную ниже команду 15 раз, поскольку на каждом из гибких дисков располагается уникальный образ копии. tar xvf /dev/fd048ds9 Такие повторные вводы команды раздражают, но они могут облегчить жизнь, что мы вскоре и увидим. Команда cpio является следующим поколением команд копирования. Ее общие функции подобны функциям команды tar, но имеется несколько важных отличий. Во-первых, вы должны сгенерировать список файлов для cpio, что означает использование команды find для порождения списка. Поскольку мы можем конвейером пропустить список, полученный от команды find, через команду sort, нам нет необходимости делать еще одну копию всех наших файлов для получения отсортированного списка. Далее, команда cpio не выполняет выравнивание границу килобайта. Она пакует все данные непрерывно и имеет магическое число в заголовке для указания начала каждого нового файла. В команде cpio также нет ука- зания размера образа на носителе. Как она это узнает? Драйвер уст- ройства должен определить размер и послать соответствующий сигнал об- ратно команде cpio, которая после этого приостанавливается и предлагает вставить следующую дискету. Это все прекрасно до тех пор, пока вы не попадаете в систему, в которой драйверы ужасны, как в системе XENIX. Драйверы XENIX не распознают, когда нужно остановиться и продолжают вы- полнять работу и после того, как достигнут конец гибкого диска. Прог- рамма cpio должна была бы выполняться одинаково на всех системах, но она не работает корректно на машинах с системой XENIX. Одно существенное различие между командами cpio и tar заключается в получающемся образе копии. Поскольку cpio не различает границ между различными носителями (дискетами), файлы получаются разорванными между двумя гибкими дисками. Это значит, что когда вы пытаетесь копировать с этих 15 дискет, то они являются ОДНИМ непрерывным потоком входных дан- ных, точно так, как и последовательный поток выходных данных при созда- нии этой копии. Что произойдет, если дискета номер 2 повредится? ВСЕ ваши файлы после второй дискеты стали бесполезны. Вы просто потеряли весь ваш образ копии. Поскольку tar копирует в виде отдельных образов, когда дискета номер 2 потеряется, вы все равно можете копировать с дискет 3-15 без проблем. Еще один прекрасный аспект команды cpio заключается в том, что она может работать как в формате файловой системы, так и в потоковом форма- те. Формат файловой системы (опция -p) обращается к блочным уст- ройствам, таким как жесткий диск, а потоковый формат обращается к нест- руктурированным устройствам (опции -i и -o), таким как магнитная лента или гибкий диск с форматом низкого уровня. Cpio - это прекрасная утили- та для использования ее при копировании файловых деревьев системы на жестком диске. Как же управляется с этим система 4.2 BSD? В течение многих лет применялась команда tar для пересылки туда и обратно, как описано на страницах руководства по tar(1). Не самый элегантный подход, но рабо- тоспособный. Сейчас они имеют ключ -r (для рекурсивного обхода дерева сверху вниз) для обычной команды cp. Я же по-прежнему считаю, что ко- манда cpio лучше. 3.4. Средства проверки операций копирования 3.4.1. dsum - контрольные суммы двух катологов ------------------------------------------------------------- Имя: dsum _____________________________________________________________ dsum Контрольная сумма двух каталогов НАЗНАЧЕНИЕ Выдает на экран выходные данные команды sum системы UNIX для двух копий файлов из двух разных каталогов в одной строке. Это позволяет быстро визуально оценить, одинаково ли содержание файлов и может быть использовано для проверки копии. ФОРМАТ ВЫЗОВА dsum [-c|-o] control_dir backup_dir Пример вызова dsum $HOME/bin /mnt Просматривает, были ли какие-либо файлы изменены при копировании из моего регистрационного каталога на гибкий диск, смонтированный в ка- талоге /mnt. Командный файл dsum 1 : 2 # @(#) dsum v1.0 Dual directory sum Author: Russ Sage 4 if [ $# -lt 2 -o $# -gt 3 ] 5 then echo "dsum: invalid argument count" >&2 6 echo "usage: dsum [-c|-o] control_dir backup_dir" >&2 7 echo " -c = C source files, -o = object files" >&2 8 exit 1 9 fi 11 case $# in 12 2) FLIST=*;; 13 3) case $1 in 14 -c) FLIST=*.c;; 15 -o) FLIST=*.o;; 16 *) echo "dsum: invalid argument $1" >&2 17 echo "usage: dsum [-c|-o] control_dir bacup_dir" >&2 18 exit 1;; 19 esac 20 shift;; 21 esac 23 for FILE in $1/$FLIST 24 do 25 BASEF=`basename $FILE` 26 if [ `expr $BASEF : '.*'` -lt 7 ] 27 then echo "`$BASEF: \t'`sum $FILE | cut -d' ' -f1`\t\c" 28 else echo "$BASEF:\t`sum $FILE | cut -d' ' -f1`\t\c" 29 fi 30 sum $2/$BASEF | cut -d' ' -f1 31 done Переменные среды выполнения BASEF Содержит базовое имя файла из полного маршрутного имени FILE Содержит имя каждого проверяемого файла FLIST Содержит указание на тип проверяемых файлов Описание Зачем нам нужен dsum? В среде разработки программ всегда имеется масса файлов. Эти файлы содержат все: исходный код, перемещаемые модули, объектный код, данные, тексты. Другим аспектом среды разработки программ является то, что эти файлы обычно рассыпаны по многим различным машинам (или группам машин, может быть и такой случай). В этом случае всегда кажется, что имеется очень много перемещений файлов: эти файлы передаются из одной системы на другую, некоторые модифицируются, пересылаются обратно и так далее. Похоже на то, как в армии роют ямы: вы делаете это, потому что вам при- казано. Когда вы перемещаете много файлов, то какой путь является лучшим для того, чтобы гарантировать себе (или кому-либо еще), что выполненная вами копия является ТОЧНО такой, как и оригинал? Если вы внесли ошибку в первоначальную копию, затем распространили эту ошибку на многие копии или даже записали вместо оригинала модифицированную копию, то вы можете никогда не вернуться в первоначальное состояние. Одним из способов слежения за копиями является использование ко- манды sum. Эта команда читает данные и выводит число, являющееся разно- видностью контрольной суммы. Другими утилитами UNIX, которые делают что-то подобное, являются cmp для сравнения объектных файлов и diff для обнаружения различий в текстовых файлах. Автор привык думать, что sum будет сообщать об отличии даже в од- ном бите (своего рода циклическая избыточная проверка), но это оказа- лось совсем не так. Недавно имелся 35 Кбайтный файл, содержащий в виде длинного формата список файлов, которые должны были быть скопированы. В действительности, там были два файла, один из которых был отсортирован, а другой нет. Они были одного размера, и sum выдала одно и то же число для обоих файлов. Когда же cmp сравнила эти два файла, оказалось, что 39-е байты отличаются. Как мы можем объяснить тот факт, что sum рассматривала эти два файла как совершенно одинаковые? Возможно, sum свернула эти два файла таким образом, что контрольная сумма оказалась одинакова, даже хотя один из файлов был отсортирован, а другой нет. Это значит, что sum на самом деле не выполняет контрольную провер- ку каждого бита. Только проверка алгоритма работы программы в исходном модуле позволит убедиться в этом. Конечно, в большинстве случаев, если файл отличается от оригинала, то это не является простой перестановкой данных, так что sum все-таки полезна. Что делает dsum? Dsum - это утилита, которая выполняет проверку после копирования. Она предполагает, что файлы скопированы из каталога-источника в ката- лог-приемник. Каталог-источник назван управляющим каталогом, поскольку он следит за тем, какие файлы сравниваются. Для каждого файла в управ- ляющем каталоге печатается его имя вместе со значением его контрольной суммы и со значением контрольной суммы для скопированного файла в ката- логе-приемнике. Вся эта информация выдается в одной строке. Польза от получения всей информации от dsum в одной строке заклю- чается в том, что визуально два файла могут быть проверены очень легко. Вам нет необходимости смотреть в другое место для получения необходимой информации. Альтернативой для dsum может быть выполнение какого-либо сценария, подобного приводимому ниже. 1. Скопируйте ваши файлы в другой каталог. 2. Подсчитайте контрольную сумму всех файлов из управляющего каталога и выведите результат в какой-либо файл. 3. Подсчитайте контрольную сумму всех файлов в каталоге, содержащем копию, и выведите результат в какой-либо файл. 4. Сравните эти два файла командой diff для того, чтобы увидеть, не отличаются ли какие-либо копии. Это не дает вам даже хорошего вывода на экран о том, что происхо- дитгиЭто не дает вам даже хорошего вывода на экран о том, что происхо- Dsum не проходит вниз по дереву файлов, потому что большинство ко- пий являются копиями из каталога в каталог, а не из сегмента дерева в сегмент дерева. Из-за того, что она не выполняет такой обход, сложность программы существенно понижается. По умолчанию сравниваются ВСЕ файлы. Это предполагает, что вы ско- пировали все файлы в каталог копирования. В некоторых случаях вы можете захотеть копировать только выбранные файлы, такие как *.c (все ваши исходные файлы). В этом случае управляющий каталог содержит множество файлов, а каталог с копиями содержит только файлы с расширением .c. Для поддержки таких случаев в программу включены ключи -c и -o. Ключ -c указывает только файлы типа *.c из управляющего каталога. В ре- зультате производится проверка только файлов *.c в каталоге с копией. Ключ -o выполняет то же самое для файлов, соответствующих *.o. Примеры 1. $ mount /dev/fd0 /mnt $ cp /usr/include/* /mnt $ dsum /usr/include /mnt Монтирует гибкий диск в каталог /mnt. Копирует все файлы заголов- ков в каталоге /usr/include на гибкий диск. Проверяет копии, используя dsum для исходного каталога и для каталога с копией. Примечание: Указывая копировать *, мы вообще не попадем в каталог /usr/include/sys. 2. $ dsum . .. Используя в качестве управляющих файлов файлы в текущем каталоге, сверить каждый файл с одноименным файлом в родительском каталоге. Пояснения В строках 4-9 производится проверка на наличие ошибок. Если указа- но менее двух аргументов, значит управляющий каталог и/или каталог ко- пии не указан и в результате обнаруживается ошибка. Если количество ар- гументов превышает три, значит, указано еще что-то кроме ключа -c и двух каталогов, что также является ошибкой. Все остальное (два или три аргумента) рассматривается как допустимое значение. В строках 11-21 производится инициализация переменной FLIST. FLIST - это управляющая переменная, которая определяет имена файлов, на кото- рые надо обратить внимание. Если в командной строке указаны только име- на каталогов ($# = 2), FLIST присваивается значение по умолчанию * (все файлы) в строке 12. Значение * присваивается переменой FLIST и не трак- туется в это время как метасимвол (это особенность командного процессо- ра). Если в командной строке указан ключ ($# = 3), производится провер- ка первой переменной и FLIST получает соответствующее значение, *.c или *.o. Если указана не такая опция, выводится сообщение об ошибке и прог- рамма завершается. В строках 23-31 выполняется сама работа. Здесь выполняется цикл for, который проходит по списку слов, созданному управляющим каталогом в соответствии со значением переменной FLIST. В строке 23 переменная FLIST расширяется фактически с символа * в имя каждого файла. Тем самым цикл for получает данные для использования. Следовательно, переменная FLIST является полным маршрутным именем каждого файла в управляющем ка- талоге. Строка 25 разбирает расширение, сделанное в строке 19. Переменная BASEF получает базовое имя полного маршрута из переменной FILE. Причи- ной, по которой мы это делаем, является тот факт, что позже при ссылке на каталог копии нам необходимо только имя файла. (В системе UNIX ко- манда basename возвращает последний элемент в указанном маршруте, т.е. само имя файла, если маршрут содержит промежуточные каталоги.) Строки 26-29 выводят первую часть выходного сообщения. Оператор if-then использован потому, что нам нужно менять выходное сообщение в зависимости от того, сколько символов содержит имя файла. Строка 26 оп- ределяет длину имени файла, используя команду expr. Команда expr может быть использована для сравнения двух строк и получает количество сов- павших символов. Сравнение имени файла со "всеми символами" (*), таким образом возвращает длину строки. (У вас может возникнуть желание обра- титься к expr(1), чтобы получить информацию о других хитростях этой многоцелевой команды.) Это возвращаемое значение используется в операторе test для опре- деления, содержит ли имя файла менее семи символов: возможно всего один или два символа. В последнем случае, если мы делаем табуляцию, мы полу- чим только первую позицию табуляции. Для получения последующих табуля- ций мы отображаем семь символов для того, чтобы попасть на место следу- ющего поля табуляции. (Если было 3-6 символов, мы все равно остановимся на поле второй табуляции, т.е. это место работает верно.) Затем отобра- жаем табуляцию для того, чтобы мы попали на место окончания второй та- буляции, что нам и требовалось. Если имя файла содержит более семи символов, мы уже находимся в первой позиции табуляции или за ней. Таким образом, следующий символ табуляции передвинет нас во вторую позицию табуляции. Эффект заключа- ется в том, что для размещения колонок не имеет значения размер имени файла (кроме случая, когда оно действительно очень длинное). Это позво- ляет избавиться от "блюза ползущих колонок", когда колонки сдвигаются в зависимости от размера отображаемой информации. В качестве примера та- кого эффекта может служить стандартная команда sum. Ее выход выглядит так: -------------------------------- | 4243 3 autobkp | 247 1 can | 25167 6 cpiobr | 186 3 dosflp | 56864 2 dsum | 2782 1 log | С другой стороны, выход dsum очень ясный и четкий, не сдвигается по всему экрану. Сдвиг делает вывод изломанным и затрудняет быстрый просмотр информации. Чудо вывода в одну строку совершается в строке 27 (для файлов с именами менее 7 символов) или в строке 28 (для файлов с более длинными именами). Внутри команды echo в каждом случае мы прячем другие команды, но по-прежнему управляем тем, как их результаты выводятся на экран. Во-первых, сами имена файлов выводятся, будучи ранее извлеченными из полного маршрутного имени. Обратите внимание, что имена файлов не со- держат информацию о том, из какого они каталога. Затем мы несколько сдвигаемся и печатаем первое поле выходной суммы для этого файла (саму контрольную сумму). Это контрольная сумма версии файла в управляющем каталоге, поскольку переменная FILE была сгенерирована для этого ката- лога. Команда sum выводит три значения (контрольная сумма, число бло- ков, занятых файлом, и само имя файла). Нам нужно получить только пер- вое значение, которое извлекается путем выполнения команды sum и пере- дачи ее выхода по каналу команде cut, которая и возвращает первое поле. После того, как значение контрольной суммы напечатано, мы отображаем \c для запрещения перехода на новую строку. Это сохраняет курсор в той же строке. Здесь начинает работать строка 30. Она генерирует контрольную сум- му того же файла в каталоге копии ($2 в командной строке) и текущее имя файла, вырезая только число, и печатает его правее курсора в той же строке. Цикл завершается, когда все файлы из управляющего каталога будут проверены командой sum. ВОЗМОЖНЫЕ МОДИФИКАЦИИ КОМАНДНОГО ФАЙЛА Неплохой модификацией может быть вариант, чтобы dsum печатала не только значения, как это сделано, но и флаг в конце строки в случае, когда два файла отличаются. Тогда у нас не было бы необходимости просматривать непосредственно числа. Мы ищем флаг и сразу видим, когда что-то не так. Ниже приводится решение проблемы вывода флага для того, чтобы об- легчить вам работу. Оно не включено в представленную выше утилиту по той причине, что такого рода вещи могут быть выполнены относительно просто. Вы можете сами сделать эту модификацию в качестве упражнения. Текст программы, выполняющей эту работу, выглядит так: for FILE in $1/$FLIST do BASEF=`basename $FILE` S1=`sum $FILE 2>&1 | cut -d' ' -f1` S2=`sum $2/$BASEF 2>&1 | cut -d' ' -f1` if [ "$S1" = "$S2" ] then M="" else M="<---" fi if [ ` expr $BASEF : '.*'` -lt 7 ] then echo "$BASEF: \t$S1\t$S2 $M" else echo "$BASEF:\t$S1\t$S2 $M" fi done Подход к решению немного отличается от решения, принятого при на- писании dsum, поскольку вы не можете генерировать контрольную сумму на ходу. Вы должны перехватить выход команды sum и использовать его позже. То, что мы ищем, появляется в шестой строке. Если две контрольные суммы различны, переменная M устанавливается соответствующим образом. Если файлы различаются, переменная M получает стрелку, указывающую на то, что копия плохая. 3.4.2. log - меню доступа к файлам протокола копирования -------------------------------------------------------------------------- Имя : log _________________________________________________________________________ log Меню доступа к файлам протокола копирования НАЗНАЧЕНИЕ Обеспечивает интерфейс в виде меню к файлам протокола, полученным от утилиты autobkp. ФОРМАТ ВЫЗОВА log Пример вызова log Командный файл log 1 : 2 # @(#) log v1.0 Menu access to backup logfiles Author: Russ Sage 4 c 5 set `date` 6 echo " 8 $1, $2 $3 $4 10 Logfile Menu 11 ---------------- 12 1 - list all log file names 13 2 - display log of home backup 14 3 - display log of product backup 15 to exit 17 Enter command (1-3,<>): \c" 18 read CMD 20 case $CMD in 21 "") exit;; 22 1) echo "\nLogfile names:" 23 sed -n -e "/more/s/^.*more \(.*\);;$/\1/p" $HOME/bin/log;; 24 2) more $HOME/bin/autobkplog.home;; 25 3) more$HOME/bin/auto2.bkplogm;; 26 *) echo "log: $CMD is not a command";; 27 esac Переменные среды выполнения CMD Команда, полученная от пользователя HOME Ваш регистрационный каталог в системе Описание Зачем нам нужен log? Если вы читали эту главу, ничего не пропуская, вы уже встречались с программой autobkp. Выводные данные autobkp очень информативны и должны быть сохранены как часть операции копирования. Это еще более важно, если у вас имеются программы, запускаемые с помощью cron. Со временем некоторые из этих программ может начать работать неверно и разрушить все вами сделанные копии. Единственный способ проследить за такими вещами - это использовать файлы протокола. Файлы протокола ко- манды cron содержат некоторую информацию, но файлы протокола программы autobkp содержат ее гораздо больше. Проблема возникает, когда вы сталкиваетесь с наличием нескольких работ для autobkp. Вы можете не захотеть смешивать невзаимосвязанные копии файлов в одном и том же файле со списком маршрутов, поэтому вы создаете несколько файлов pathlist, несколько заданий для cron и несколько файлов протокола. Если вам нужно выполнить пять или десять подобных работ, каким образом вы проследите за всеми файлами протоко- лов, запомните их имена и облегчите их просмотр? Все эти проблемы реше- ны в командном файле log. Что делает log? Командный файл log - это управляемая при помощи меню утилита. Меню позволяет вам видеть текущие имена файлов в файлах протокола, поэтому вам нет необходимости помнить их. Остальные команды являются входными точками в файлы протокола, использующими команду more для просмотра. Когда мы рассматривали командный файл cpiobr, мы видели, как рабо- тает управляемая при помощи меню программа. Командный файл log несколь- ко проще в том смысле, что он делает только один проход. Кроме того, log - это "живая" программа. Она не является статичной и должна посто- янно изменяться в соответствии с вашими процедурами копирования. Посредством такой модификации log способна сообщить вам действительные имена файлов. Log является разновидностью программы, ссылающейся сама на себя. Для показа действительных имен файлов она просматривает свое содержимое и выбирает (используя sed) имена log-файлов из команды more, которая выводит их. Поскольку вы добавляете файлы протокола, программа log мо- жет хранить текущие, потому что она просматривает сама себя для опреде- ления того, что соответствует действительности. Применяя процедуру по- иска таким образом, мы избавляемся от необходимости сохранять отдельные файлы данных с именами в них или использовать какие-то соглашения об именовании выполнения той же задачи. Способность программы log обра- щаться самой к себе позволяет вам добавлять неограниченное число файлов протокола в список, и вам предоставляется свобода по выбору имен таких файлов. Возможно, вы заметили, что стратегия, использованная в командном файле log, может быть использована для обеспечения вывода на экран лю- бого набора файлов (записные книжки, документация или еще что-то). Все, что вам нужно сделать для этого - записать их в соответствии с командой more и добавить столько команд в главное меню, сколько вы хотите. Пояснения Строка 4 очищает экран, используя команду c, представленную ниже в этой книге. (Вместо этого вы снова можете использовать команду clear, если она доступна.) Строка 5 устанавливает в позиционные параметры выход команды date. Это то же самое, что мы делали в программе cpiobr. Строки 6-17 выводят меню. Здесь использован один оператор echo, как описано в cpiobr. Стро- ка 13 читает команду пользователя. Строки 20-27 выполняют основную работу программы. Если введенная команда была просто возвратом каретки (трактуется как нуль), программа завершается. В строке 23 команда sed просматривает файл $HOME/bin/log. Это требует, чтобы вы поместили log в подкаталоге двоичных модулей ва- шего регистрационного каталога. Если вы разместите ее где-либо в другом месте, вы должны изменить эту строку. Команда sed использует ключ -n, который запрещает вывод, за исключением того, что явно указано для пе- чати. Строка -e находит имена файлов. Данный подход использует функцию замены в команде sed. Таким обра- зом мы можем заменить все за исключением имени файла, а затем напеча- тать его. Смысл этой записи примерно такой: сперва мы ищем выражение more (/more/), находя тем самым все строки в файле протокола, содержа- щие слово "more". По определению, каждый файл протокола выводится на экран, используя команду more. Поскольку вы добавляете файлы протокола, каждая новая строка должна содержать слово more, поэтому файлы нахо- дятся автоматически по выражению команды sed. Затем мы указываем команде sed сделать замену. Первое выражение содержит в себе всю строку от начала до конца, но мы применяем круглые скобки для отметки внутри нее образца .*, тем самым выделяя часть стро- ки между пробелом после "more" и первой точкой с запятой в конце стро- ки. Если вы посмотрите на все строки в файле log, которые начинаются с "more", то вы увидите, что это соответствует имени файла, которое мы ищем. Затем мы указываем команде sed заменить всю строку на первый обра- зец "pattern 1". "Pattern 1" - это запись команды sed для первого отме- ченного или "отмеченного биркой" выражения. Другими словами, мы замени- ли имя файла на всю строку целиком и указали команде sed напечатать ре- зультат, тем самым выдавая на экран имя файла. Эта работа выполняется для такого количества операторов more, сколько вы имеете. Чем больше файлов log вы имеете, тем больше файлов обрабатывает команда sed. Обратите внимание, что оператор sed просмат- ривает любое количество символов от начала строки для нахождения слова "more". Не указывая в программе конкретное число символов, на которое нужно отступить, вы получаете тем самым свободу выбора ваших собствен- ных уровней отступа. Если введенная команда не является допустимой, выдается сообщение об ошибке. Эта программа не имеет цикла, поэтому срабатывает один раз. Если вы хотите запустить ее снова, вы должны снова ввести log. Пример $ log 1 После запуска программы выводится меню. Введите число 1 для того, чтобы увидеть все имена log-файлов. Теперь, когда мы изучили, как распознавать и управлять файлами во- обще, давайте рассмотрим некоторые систематические методы управления ИНФОРМАЦИЕЙ в файлах. Мы начинаем в следующей главе с файлов, которые важны для нас как для программистов.  * ГЛАВА 4. Управление программной документацией *  ВВЕДЕНИЕ 4.1. ПРОГРАММИРОВАНИЕ и УПРАВЛЕНИЕ ДОКУМЕНТАЦИЕЙ 4.2. ИЗВЛЕЧЕНИЕ ДОКУМЕНТИРУЮЩИХ ЗАГОЛОВКОВ 4.2.1. stripc - из файла на языке Си 4.2.2. stripf - из Си-функции 4.2.3. strips - из командного файла Shell 4.3. ctags - создание файла признаков исходного кода проекта ВВЕДЕНИЕ Вы решили рискнуть. Продукт на три месяца опаздывает в произ- водство и нуждается лишь в крохотной доработке. Вы уверены, что знаете, как работает функция, которая открывает входной буфер. Вы ее недавно использовали. Вы увеличиваете размер буфера в вызове функции и запуска- ете быстрый тестик. Все в порядке, поэтому вы окончательно собираете поставку на диске и отправляете ее в производство. Месяц спустя, начи- нают поступать сообщения от разгневанных заказчиков. Похоже, что если текстовый процессор, электронная таблица и база данных открыты все вместе и активны одновременно (что является одним из больших товарных достоинств вашего продукта), то просто новый буфер настолько велик, что поглощает ключевой раздел памяти и превращает высоко летающее чудо ин- тегрированного программного обеспечения в яркую руину. Почему вы не проверили документацию по этой функции? Выяснение то- го, в каком файле находится документация, заняло бы определенное время, а поскольку документацию так трудно сопровождать, то связанные с ней вещи так или иначе устаревают. Тем не менее, аналогичные провалы не должны возникать. Программирование - тяжелая работа, но это только половина работы. Хорошая документация очень важна, если вы собираетесь иметь возможность сопровождать ваш программный код, но и управление всей документацией, связанной с большим программным проектом также является тяжелой рабо- той. Происходят постоянные изменения, и обычно отсутствует единообразие подхода. Документирование исходных файлов на Сикак в целом, так и по каждой функции является хорошим первым шагом, но такая документация не очень полезна, если вы вынуждены пробираться через дюжины файлов, чтобы обнаружить, как называется конкретная функция или какие функции состав- ляют данный модуль. Если вы хотели бы изучить еще одно средство, связанное с разработ- кой, см. программу cg в главе 10. 4.1. ПРОГРАММИРОВАНИЕ И УПРАВЛЕНИЕ ДОКУМЕНТАЦИЕЙ В данной главе представлен набор командных файлов командного про- цессора для извлечения документирующей информации из исходного кода программ на Си и командных файлов командного процессора. Используются две стратегии. Первая состоит в том, что, следуя стандартной "модели документации" в исходном коде, вы можете придумать командные файлы, ко- торые просто "вытягивают" самые новые разделы с заголовочной информаци- ей из файлов с исходным кодом и собирают их затем в новый файл. Такие файлы служат в качестве каркаса для документации по программе. Следова- тельно, при условии, что заголовки исходного кода изменяются разными программистами стандартным образом, простая команда UNIX может извлечь полностью новый каркас руководства. Этот подход реализуют командные файлы stripc, stripf и strips. Stripc и stripf предоставляют листинги блоков документации уровня файла и уровня функций из ваших исходных файлов на Си, а strips извлекает доку- ментацию из командных файлов командного процессора. Второй подход - доступ к определенным видам структур (таким как функции на Си) в теле самого программного кода. Этим методом вы можете точно найти, как называется данная функция, без сосредоточенного изуче- ния горы листингов. Командный файл ctags является и полезным инструмен- том, и моделью применения этого подхода к другим видам программных структур. Ctags объединяет свой выводной файл с редактором vi/ex с целью предоставления простого способа доступа к любой заданной функции и ее просмотра, копирования или редактирования в текущей программе. Ctags делает это путем предоставления признаков, которые понимает vi, для каждой функции, обнаруженной в любом указанном наборе файлов. Таким об- разом, вы можете использовать простую команду редактора, чтобы получить то, что вам нужно. Вы больше не обязаны заботиться о том, какой файл содержит какую функцию. Ctags - отличный пример применения мощи UNIX в полном объеме. Имея такие инструментальные средства, вам не нужно изобретать ко- лесо, так как вы можете легко находить и выбирать те средства, которые необходимы вам в конкретном приложении. Вы уже написали программу уп- равления терминалом Trantor TR-101? Примените ctags и найдите ее. Более того, самодокументируемый напечатанный файл и документация о функциях, полученная с помощью этих командных файлов, дают другим программистам хороший старт в понимании того, что вы сделали. Это даже может слегка произвести впечатление на вашего начальника. Каким в общих чертах будет наш подход к созданию таких командных файлов? У нас есть некоторые потенциальные преимущества в применении такого вида доступа в системе UNIX. Прежде всего, исходные файлы не от- личаются от других текстовых файлов, поэтому мы можем использовать все имеющиеся в UNIX средства поиска и распознавания шаблонов (sed, awk и т.д.), чтобы находить символьные строки. Во-вторых, мы освоили технику обхода файловых деревьев и работы с отобранными типами файлов, описан- ную в предыдущих главах. Наш подход состоит в объединении этих средств таким образом, чтобы они обеспечивали доступ к структурированной доку- ментации, содержащейся в программных файлах. 4.2. Извлечение документирующих заголовков 4.2.1. stripc - из файла на языке Си ------------------------------------------------------------ ИМЯ: stripc ------------------------------------------------------------ stripc Извлекает документирующий заголовок из исходного файла на языке Си. НАЗНАЧЕНИЕ Печатает первый блок строк комментария в файле с исходным кодом на Си так, чтобы вы могли быстро идентифицировать назначение программы на Си. ФОРМАТ stripc файл [...] ПРИМЕР ВЫЗОВА stripc prog*.c > header Извлекает начальные блоки комментариев из всех файлов и помещает в один файл с именем header. ИСХОДНЫЙ КОД ДЛЯ stripc 1 : 2 # @(#) stripc v1.0 Strip comment header Author: Russ Sage 4 if [ "$#" -eq "0" ] 5 then echo "stripc: arg count error" >&2 6 echo "usage: stripc file [...]" >&2 7 exit 1 8 fi 10 for FILE in $@ 11 do 12 if [ ! -s $FILE ] 13 then echo "file \"$FILE\" does not exist" >&2 14 continue 15 fi 17 awk '/^\/\*/, /^ \*\// { if ($0 != " */") 18 print 19 else {print;exit} 20 }' $FILE 21 echo "^L" 22 done (Перед тем как вводить этот исходный код, обратите внимание, что в строке 21 должен быть действительно символ control- L, введенный между двумя кавычками, по причинам, рассмотренным ниже.) ПЕРЕМЕННАЯ СРЕДЫ FILE Хранит имя файла, полученное из командной строки. ОПИСАНИЕ ЗАЧЕМ НАМ НУЖЕН stripc? В больших проектах по разработке программного обеспечения требу- ется обычно много времени для работы с документацией. Имеются программ- ные файлы для документирования, функциональные спецификации для написа- ния программ и, наконец, руководства и справочные карты, глоссарии, указатели и т.д. Настоящий программный код должен иметь свою собствен- ную встроенную документацию, иначе управление этим кодом становится очень трудным. Чтобы избежать путаницы, нужно создать модель документации, а за- тем сделать ее стандартом, которому должны следовать все программисты. Даже если эта модель не будет абсолютно идеальной, ее наличие является первым шагом по созданию среды, которой вы можете управлять. Следующие два инструментальные средства, которые мы предлагаем, следуют модели документации, описанной в дальнейшем тексте. Эта модель последовательна и понятна, ее можно дополнить или изменить по вашему усмотрению. ЧТО ДЕЛАЕТ stripc? Stripc печатает только первый заголовочный блок комментариев из начала исходного файла на языке Си. Желательно, чтобы этот блок содер- жал всю важную информацию о файле: его официальное имя, для чего он предназначен, кто его создал, когда он был создан и т.д. Внутри файла может размещаться одна или несколько функций или даже главная програм- ма. Эта модель предполагает, что весь ваш код содержит очень мало глав- ных программ и много независимых модулей. Рассмотрим на модельном исходном файле, какого рода информацию мы должны извлечь из исходных файлов. /* * Это документирующий заголовок для файла * с исходным кодом на языке Си. * Он поясняет, что содержится в файле (программы, функции, * библиотеки и т.д.) и идентифицирует проект. * */ Это отметка конца заголовочного комментария. ^L Инструменты извлечения применяют control-L как разделитель. /* Это документирующий заголовок для главной части программы. * Главная пометка должна объяснять, что это за программа * и что она делает. Здесь могут быть также указаны автор, * дата и история изменений. */ main() { /* Здесь находится главная Си-программа */ } ^L /* Это документирующий заголовок для определенной функции, * которая за ним следует. Документируется последователь- * ность вызова, вход и выход и общее назначение этой * функции. */ func(arg1,arg2) int arg1; char arg2; { /* Текст функции находится здесь */ } ^L /* Аналогично, этот блок комментариев документирует * следующую функцию. Наличие документации вместе с кодом * сокращает объем накладных расходов при чтении и * изменении кода. */ func(arg1,arg2) int arg1, arg2; { /* Текст функции находится здесь */ } Как указывалось ранее, функция main не обязательна и, вероятно, встречается только в одном или двух файлах, в зависимости от вида прог- рамм, которые вы пишете. Один файл может иметь столько функций, сколько вы хотите, но рекомендуемое максимальное число - от одной до трех, в зависимости от того, как эти функции взаимосвязаны. В каждом файле имейте дело только с одной программируемой идеей и ее реализацией. При изучении этой модели вы видите, что обеспечивается три уровня документации. Заголовок в начале файла извлекается с помощью stripc. Этот заголовок относится ко всему файлу в целом. Заголовок в начале главной программы относится ко всей программе и поддерживается с по- мощью stripf. Заголовок для каждой функции относится к этой функции. Эти заголовки обслуживаются командным файлом stripf, который обсужда- ется ниже. Отметим, что между функциями имеется прогон формата (символ control-L кода ASCII). В предыдущем листинге мы указали эту комбинацию клавиш с помощью символа ^L, чтобы наши текстовые процессоры не произ- водили лишних страниц при форматировании рукописи данной книги. Вам нужно в каждом случае действительно вводить control-L вместо ^L при размещении комментариев в ваших файлах и при вводе исходного кода дан- ного и последующих командных файлов. Символ прогона формата использу- ется в модели заголовка для отметки верхней границы первой функции в файле и для прогона страниц на печатающем устройстве при чистовой распечатке, чтобы каждая функция появлялась на новой странице. В начале каждой функции (перед main или перед именем функции) дол- жен существовать документирующий заголовок. Этот заголовок обычно отра- жает наиболее недавние изменения в этом модуле, и его можно считать бо- лее достоверным, чем заголовок документа, который был напечатан несколько недель или даже месяцев назад. Входом для stripc является последовательность имен файлов с исход- ным кодом. Для каждого файла в командной строке проверяется, существует ли он и имеет ли размер больше, чем ноль байт. Если он не удовлетворяет этим критериям, то печатается сообщение об ошибке и проверяется следую- щий файл. Каждый файл читается с первого байта, и в нем ищется символь- ная строка начала комментария (/*). Когда она найдена, информация до символьной строки конца комментария (*/) построчно выводится в stdout. Если правые символы не найдены, ничего не печатается, но сообщение об ошибке не выводится, чтобы не испортить выводную информацию. После того как каждый файл обработан, в конце печатается прогон формата, который разбивает выводную информацию на страницы-разделы. Это применяется в основном, когда документирующие заголовки очень длинные и нуждаются в визуальной разбивке. Отметим, что "извлечение" ("strip") здесь и в следующих двух ути- литах означает не УДАЛЕНИЕ, а копирование соответствующей информации. Никаких изменений во входных файлах не делается. Когда все файлы в командной строке обработаны, командный файл за- вершается. ПРИМЕРЫ 1. $ stripc test?.c > test.filehdrs Извлекает блок файловых комментариев из всех файлов, соответствую- щих строке "test", за которой следуют один произвольный символ, а затем .c. Сюда подходят test1, testA, testb и т.д. Все блоки комментариев по- мещаются в файл test.filehdrs в таком порядке, как они обрабатывались бы в цикле for. 2. $ for DIR in src1 src2 src3 > do > stripc $DIR/*.c > /tmp/$DIR.hdrs > done Этот цикл проходит каждый из трех каталогов src1, src2 и src3. В каждом из них имеются исходные файлы, из которых нужно извлечь докумен- тирующие заголовки. Каждый раз берется один каталог, и stripc вызыва- ется для всех исходных файлов на языке Си из данного каталога. Выход, т.е. все документирующие заголовки из файлов в этом каталоге, помеща- ется в файл в каталоге /tmp. Файлы в /tmp имеют имена /tmp/src1.hdrs, /tmp/src2.hdrs и /tmp/src3.hdrs. Отметим, что число файлов, передавае- мых stripc, имеет ограничение в 255 символов. После этого командная строка переполняется и выполнение командного файла аварийно заверша- ется. ПОЯСНЕНИЯ Строки 4-8 делают проверку на ошибки. Если число параметров в ко- мандной строке нулевое, возникает ошибка. При вызове stripc должно быть хотя бы одно имя файла. Цикл for в строках 10-22 пробегает по каждому имени файла из списка позиционных параметров в командной строке. Первым делом, в строках 12-15 проверяется существование файла. Если файл не существует, выдается соответствующее сообщение об ошибке и цикл продолжается со следующим файлом. Строки 17-20 - это цикл awk, который делает всю важную работу. Входным для awk является имя файла, которое сейчас обрабатывает цикл for. Если вы не очень знакомы с программой awk, сообщим, что она вызы- вается так: awk программа имя-файла где программа состоит из последовательности предложений, имеющих вид: шаблон { действие } Указанное действие применяется к тексту, который соответствует шаблону. Для того чтобы утилита awk работала корректно, вы должны зак- лючить всю программу в одинарные кавычки. В stripc для указания начала комментария (/*) и конца комментария (*/) используются соответственно шаблоны /^\/\*/ и /^ \*\// Для интерпретации этих обозначений нужно вспомнить регулярные вы- ражения ed, sed и grep. Регулярные выражения (РВ) должны быть ограниче- ны (символом /). awk воспринимает два выражения, разделенные запятыми, как начальный и конечный шаблоны для квалификации вводных строк. В данном примере начальное выражение означает "от начала строки (^), вслед за действительным символом косой черты (который должен быть экранирован, чтобы убрать его специальное значение, т.е. \/) и действи- тельной звездочкой (\*), использовать все строки, обнаруженные вплоть до конечного выражения". Этим выбирается только первое вхождение со- поставляемого шаблона (т.е. первого блока комментариев), так как прог- рамма awk заканчивает работу при обнаружении завершающей строки. (Остальные блоки комментариев выбираются с помощью stripf вместе с име- нем функции и аргументами.) Для каждой строки, которая соответствует набору выражений от на- чального до конечного, выполняются предложения, указанные в "действии". Строки 17, 18 и 19 содержат предложение if-then= else, которое выполня- ет всю работу. Если $0 (что является всей строкой) НЕ равно пробелу и концу комментария (*/), печатается вся строка. Формируя таким образом предложение if, мы печатаем первую строку и все последующие строки, по- ка не доберемся до конца комментария. Когда обнаружена последняя строка, результатом проверки является значение "ложь", поэтому выполняется else-часть. Поскольку мы знаем, что это последняя строка, мы печатаем ее для завершенности блока ком- ментария, а затем выходим из awk. Отметим, что благодаря вложению этих двух команд вместе в фигурные скобки ({}), они рассматриваются как одно составное предложение. После завершения awk производится эхо-отображение прогона формата на экран и берется следующий файл. Так продолжается до тех пор, пока все файлы в командной строке не будут обработаны. 4.2.2. stripf - из Си-функции ------------------------------------------------------------ ИМЯ: stripf ------------------------------------------------------------ stripf Извлекает документирующий заголовок Си-функции. ФУНКЦИЯ Извлекает и печатает комментирующий заголовок, имя функции с пара- метрами вызова и объявление типов параметров для всех функций в исход- ном файле на Си. ФОРМАТ stripf file [...] ПРИМЕР ВЫЗОВА stripf lib1.c Извлекает документирующие заголовки для всех функций в файле lib1.c. ИСХОДНЫЙ КОД ДЛЯ stripf 1 : 2 # @(#) stripf v1.0 Strip function header Author: Russ Sage 4 for FILE in $@ 5 do 6 sed -n -e ' 7 /^L$/ { 8 s/^L$/.bp/p 9 : loop 10 n 11 /^{/b exit 12 p 13 b loop 14 : exit 15 i\ 16 {} 17 b 18 }' $FILE 19 done ПЕРЕМЕННАЯ СРЕДЫ FILE Хранит имя файла для каждого файла из командной строки. ОПИСАНИЕ ЗАЧЕМ НАМ НУЖЕН stripf? Предположим, что наш код на языке Си соответствует модели докумен- тации, представленной ранее при описании stripc. Тогда нам нужен способ поддержания изменений в документации по ходу изменений кода. Мы видели, что при хранении документации в исходных файлах более вероятно, что она будет изменена, когда изменится код. Проблема возникает, когда нам нуж- на твердая копия документации, которая находится внутри исходного кода. Как нам получить ее из файлов? ЧТО ДЕЛАЕТ stripf? Командный файл stripf решает эту проблему. Он просматривает весь файл и печатает всю документацию для каждой ФУНКЦИИ, которая размещена в этом файле (включая "main", если она есть). Входом для stripf являются имена файлов, переданные в командной строке. Stripf обрабатывает файлы по очереди и помещает выход в stdout. Этот выход можно перенаправить, но вход должен быть в командной строке. К выходу применяются дополнительные модификации, чтобы сформиро- вать данные для среды утилиты nroff, поэтому выводные файлы можно фор- матировать с помощью этой утилиты. Все прогоны формата заменяются на команду .bp, принятую в nroff для начала страницы. Эта команда nroff прогоняет страницу и увеличивает на единицу счетчик страниц. Возможно, вы не хотите менять нажатие клавиш control-L на это значение, но вы всегда можете указать в командном файле такие действия, какие вам нуж- ны. ПРИМЕРЫ 1. $ stripf module1.c | grep >"^\.bp$" | wc -l Печатает число модулей-функций, содержащихся в файле module1.c, путем поиска каждого появления команд новой страницы программы nroff и их подсчета. Мы знаем, что одна из таких команд является выходом для каждой обнаруженной функции. Данную строку можно вложить в предложение echo, которое говорит "имеется X модулей в файле $FILE". 2. $ for FILE in *.c ../*.c $HOME/src/*.c > do > stripf $FILE > done >> /tmp/func.hdrs Данный цикл for распространяется на все файлы в текущем каталоге, которые оканчиваются на .c, все файлы в родительском каталоге с таким же суффиксом и все файлы в подкаталоге src моего home-каталога с тем же суффиксом. Из каждого файла извлекается документация о функциях и нап- равляется в стандартный вывод. Весь цикл перенаправлен с помощью >>, поэтому выход каждого вызова stripf ДОБАВЛЯЕТСЯ к файлу в /tmp. ПОЯСНЕНИЯ Вся программа - это один большой цикл for в строках 4-19. Этот цикл присваивает переменной FILE каждое имя, имеющееся в командной строке. Данный командный файл не имеет опций и обработки ошибок. Команда sed системы UNIX вызывается для каждого имени файла. Прог- рамма sed читает весь вход и выводит измененный текст в стандартный вы- вод. Опция -n используется в sed для подавления всего вывода, в проти- воположность действию по умолчанию, когда все печатается. Мы используем этот флаг по той причине, что мы хотим указать программе sed, когда пе- чатать выход. Опция -e применяется, чтобы сообщить программе sed, что следующая последовательность текста между одинарными кавычками является выражением, которое нужно вычислить. Напомним, что sed - потоковый редактор, который читает одну стро- ку, сверяет ее с выражениями, затем читает следующую строку и делает все сначала. Первое, что мы ищем - символ control-L, стоящий в строке самостоятельно. Если мы не находим его, проверяется следующая строка и так далее, пока не будет обнаружен control-L. (Еще раз напомним, что вместо обозначения ^L в коде должен быть введен настоящий control-L.) Когда обнаружен control-L, он активизирует все выражение, заклю- ченное в фигурные скобки. Первым действием является подстановка команды начала страницы программы nroff, как описано ранее. Эта подстановка пе- чатается, что является самым первым выводом. В строке 9 объявлена метка "loop". Это не приводит ни к каким действиям, но устанавливает точку перехода, которая впоследствии используется. (Управляющие структуры программы sed довольно примитивны, но они позволяют описать выполняемую работу.) Строка 8 использует команду n программы sed, чтобы вызвать чтение следующей строки. Мы разобрались с первой строкой - строкой, которая содержит control-L - так что мы можем ее отбросить. В случае выполнения цикла мы видим, что sed продвигается по нашему требованию, но не прод- вигается сам. Вспомним модель документации, рассмотренную ранее. Эта модель включает документирующий заголовок для файла в целом, выполненный в обычном стиле языка Си. Модель завершается символом control-L. Этот первый блок обрабатывается с помощью stripc, как описано ранее. Мы не хотим использовать его здесь при работе со stripf. Поэтому мы сейчас должны спозиционироваться после файлового документирующего заголовка. Вслед за символом control-L имеется еще один набор из одной или более строк комментария языка Си, которые описывают функцию, следующую за ними. Далее идет само имя функции, объявление параметров и открываю- щий символ самой функции, которым является левая фигурная скобка (}). Строка 11 ищет эту фигурную скобку. Если она найдена, выполнение переходит на метку exit (строка 14). Мы можем полагать, что мы все сде- лали, если найдена левая фигурная скобка, так как этот символ должен появляться только в начале функциимодуля. Когда мы находим фигурную скобку, мы уже напечатали к этому моменту всю комментирующую информацию и заголовок функции посредством строки 12, которую мы сейчас опишем. А что если фигурная скобка появляется в поле комментария? Нет проблем, поскольку поиск фигурной скобки привязан к началу строки с помощью сим- вола ^. Он производит выражение, означающее "от первого символа в стро- ке". Мы только тогда сопоставляем фигурную скобку этому выражению, ког- да она встречается в качестве самого первого символа в строке. Затем строка 12 предполагает, что мы еще не обнаружили фигурную скобку и поэтому мы должны напечатать строку. Оператор p печатает теку- щую строку, которую обрабатывает sed. Этот вывод направляется на экран. Строка 13 - безусловный переход на метку loop. Отметим, что этот переход только изменил процесс выполнения и не привел к чтению еще од- ной вводной записи. С этим мы должны быть осторожны при управлении про- цессом выполнения в программе sed. Мы только что напечатали текущую за- пись, поэтому теперь мы должны отбросить ее и получить следующую за- пись, что означает возврат в строку 9. Этот цикл печати и чтения следу- ющей записи продолжается до обнаружения фигурной скобки, которая пере- водит выполнение на метку exit. Строка 14 - это метка exit. Когда мы попадаем на нее, мы знаем, что был обнаружен control-L, напечатан комментирующий заголовок, напе- чатаны имя функции и объявления ее параметров и найдена фигурная скоб- ка. Заметим, что фигурная скобка еще не напечатана. Когда мы находим ее, мы только делаем ветвление. Строка 15 завершает вывод, вставляя некоторый текст в выводной по- ток. Мы не можем сказать "печатать это в буквенном виде", поэтому про- исходит движение вправо по тексту, как по команде echo. Мы должны сыг- рать на правилах, установленных программой sed. Любой вывод должен быть порожден обычными командами в стиле редактора ed. Вставка текста с по- мощью команды "i" делает нам это. Отметим, что мы также вставляем сим- вол возврата каретки (или перевода строки, в зависимости от вашей осве- домленности). Он может быть определен символом обратной косой черты (\). Обратная косая черта убирает специальное значение символов и при использовании в конце строки, как здесь, означает, что специальный сим- вол, вставленный в выражение, является возвратом каретки. Вдобавок к возврату каретки, мы вставляем пару фигурных скобок. Это обозначает объявление начала-конца функции, которую мы обрабатываем. Поскольку мы вставляем текст, мы не должны говорить программе sed, что его нужно пе- чатать. Строка 17 - безусловный переход на себя, указывающий программе sed переход на вершину всего обрабатываемого выражения. Когда это происхо- дит, мы завершаем поиск еще одного control-L и начинаем весь процесс снова. Таким образом, мы можем обработать все функции из одного файла независимо от того, сколько их там. Строка 18 является концом sed-выражения и содержит также имя фай- ла, которое должно быть передано программе sed. Это является частью обычного синтаксиса, принятого в sed, но выглядит несколько неуместным, так как не выделено специальным отступом. Когда все файлы обработаны, завершается внешний цикл и заканчивается работа командного файла. 4.2.3. strips - из командного файла Shell ------------------------------------------------------------ ИМЯ: strips ------------------------------------------------------------ strips Извлекает документирующий заголовок командного процессора. ФУНКЦИЯ Печатает начальные строки комментария к командному файлу командно- го процессора, что выражено буквой "s" в имени. Первая строка игнориру- ется для совместимости с командным процессором языка Си. ФОРМАТ strips файл [...] ПРИМЕР ВЫЗОВА strips *.sh Извлекает комментарии из всех командных файлов в текущем каталоге. ИСХОДНЫЙ КОД ДЛЯ strips 1 : 2 # @(#) strips v1.0 Strip shell comment header Author: Russ Sage 4 for FILE in $@ 5 do 6 cat $FILE | (read LINE; echo $LINE 7 while read LINE 8 do 9 if [ "`echo $LINE|cut -c1`" = "#" ] 10 then echo "$LINE" 11 else exit 12 fi 13 done) 14 done ПЕРЕМЕННЫЕ СРЕДЫ FILE Хранит каждое имя файла, полученное из командной строки. LINE Хранит каждую строку вводного текста, полученную из читаемого файла. ОПИСАНИЕ ЗАЧЕМ НАМ НУЖЕН strips? Так же как нам нужны средства обработки документации для файлов с исходным кодом на Си, нам нужны и аналогичные средства для командных файлов командного процессора. Разница между извлечением комментариев из кода на языке Си и их извлечением из кода командных файлов командного процессора - в способе разграничения комментариев в исходном файле. Исходный файл на Си использует пару /* */, а командные файлы командного процессора применяют #, чтобы выделить остаток строки как комментарий. Обработка документации облегчается, когда командные файлы следуют некоторой форме стандартизованной документации. В действительности нет формального стандарта, но наиболее близкий стандарт приходит со страниц руководств по самой системе UNIX. Стандартными полями являются имя ко- мандного файла, способ его вызова, что он делает и, возможно, некоторые пометки о том, как он работает, ссылки на другие места для поиска ин- формации и сведения о том, какие файлы использует данный командный файл. Пример формата выглядит так: : # ИМЯ # strips Извлекает поля shell-комментариев # # ФОРМАТ ВЫЗОВА # strips файл [...] # # АВТОР # Russ Sage mm/dd/yy # # ОПИСАНИЕ # Данный командный файл извлекает комментирующие # заголовки из командных файлов интерпретатора shell. # # СМ. ТАКЖЕ # sh(1), cat(1) Отметим, что в первой строке имеется оператор :, который является для командного процессора нулевым оператором. Он ничего не делает, но всегда возвращает успешный статус выхода. Он находится в отдельной строке, поскольку это обозначает командный файл для Bourne shell. Если вы запускаете /bin/csh вместо /bin/sh, командные файлы могут вызвать путаницу. C Shell ищет символ # в первой колонке первой строки. Если он там есть, командный процессор считает этот файл командным файлом ин- терпретатора csh. Чтобы указать интерпретатору csh, что командный файл предназначен для интерпретатора Bourne shell, мы можем оставить эту строку пустой (что будет не слишком хорошо печататься и подлежит удале- нию) или поместить оператор, который сообщает интерпретатору C Shell, что это командный файл для Bourne shell, но ничего не делает под управ- лением Bourne shell. ЧТО ДЕЛАЕТ strips? Strips читает командный файл и печатает все комментарии с начала файла, которые являются непрерывными (т.е. печатает, пока не достигнет строки, не являющейся комментарием). Первая строка игнорируется, но пе- чатается. Когда строка не имеет символа # в первой символьной позиции, strips прекращает чтение этого файла. Командный файл должен иметь структуру комментария, аналогичную структуре, показанной в нашем предыдущем примере. Символы # должны быть в первой позиции, и каждая строка должна иметь символ #. Если у вас есть пустая строка где-нибудь в начальном блоке комментариев, strips печатает только первую часть блока. ПРИМЕРЫ 1. $ strips `kind /bin /usr/bin` Блоки комментариев извлекаются из текстовых файлов, размещенных в каталогах /bin и /usr/bin. 2. $ find / -name "*.sh" -print | while read FILE > do > strips $FILE > /tmp/doc/$FILE > done Find порождает список всех имен файлов, который попадает в strips. Выход strips направляется в каталог документации в /tmp. Окончательный выход попадает в файл с точно таким же именем, как исходный файл, толь- ко выход помещается в /tmp, поэтому никаких случайных удалений не про- исходит. ПОЯСНЕНИЯ Строки 4 и 14 окаймляют внешний цикл, который подает имена файлов данному командному файлу. Обработки или проверки ошибок нет. Пока в ко- мандной строке есть файлы, цикл продолжается. Вы можете, конечно, про- верить наличие аргументов, правильность и существование файлов. Для этого, мы думаем, вы видели достаточно примеров проверки ошибок, чтобы добавить их, куда вам нужно. Поэтому мы иногда опускаем такие фрагменты в нашем коде, чтобы сэкономить место и выделить главную функцию. Строка 6 применяет команду cat к файлу, который сейчас обрабатыва- ется. Выход направляется в конвейер, чтобы его прочитал другой shell. Новый shell получает длинную командную строку (обозначенную скобками в строках 6 и 13). Первая команда read читает самую первую строку. Поскольку мы не собираемся проверять эту строку, мы отображаем ее, а затем опускаемся в цикл последовательного чтения. Предполагается, что первая строка начинается с двоеточия, но если она начинается с символа #, она все равно печатается, так что вы не будете терять текст. Строки 7-13 являются внутренним циклом while, читающим строки текста со стандартного ввода, который был выходом команды cat. Когда текст заканчивается, прекращается и цикл while. Строки 9-12 - это строки принятия решения. Сначала мы отображаем текущую вводную строку и передаем ее по конвейеру команде cut. Выреза- ется первый символ, затем команда сравнения проверяет, совпадает ли он с символом комментария. Если да, строка отображается на стандартный вы- вод. Если это не символ комментария, то нужно достичь конца блока ком- ментариев, поэтому происходит выход из внутреннего цикла. Управление возвращается во внешний цикл (for), который стартует и берет следующее имя файла. Когда все файлы обработаны, strips завершается. 4.3. ctags - создание файла признаков исходного кода проекта ------------------------------------------------------------ ИМЯ: ctags ------------------------------------------------------------ ctags Делает файл признаков исходного кода для простоты доступа с помощью утилиты vi. ФОРМАТ ctags [файл ...] ПРИМЕР ВЫЗОВА ctags proj*.c Делает файл признаков для всего исходного кода проекта. ИСХОДНЫЙ КОД ДЛЯ ctags 1 : 2 # @(#) ctags v1.0 Create a C source code tag file Author: Russ Sage 4 awk -F'(' '/^[a-zA-Z_][a-zA-Z0-9_]*\(/ { 5 printf ("%s\t%s\t/^%s$/\n", $1, FILENAME, $0) }' $@ | sort -u +0 -1 ПЕРЕМЕННАЯ СРЕДЫ FILENAME awk Переменная, содержащая имя файла. ОПИСАНИЕ ЗАЧЕМ НАМ НУЖЕН ctags? UNIX создан как среда для разработки программного обеспечения. Она поддерживает и поощряет модульность исходного кода программы. Модуль- ность - это концепция разбиения проекта на отдельные файлы, превращения идей в подпрограммы и компиляции отдельных файлов с исходным кодом в перемещаемые модули для последующей их сборки в исполняемый модуль. Такая философия разработки программного обеспечения может, однако, породить некоторые проблемы. Главная проблема - попытка получить неко- торого рода сцепку из всех маленьких кусков головоломки. Делаются вызо- вы подпрограмм, которые находятся в других файлах, возможно даже в дру- гих каталогах. Нужен инструмент, позволяющий нам, людям, посмотреть на программное обеспечение человеческим взглядом, т.е. содержательно, а не с точки зрения физического размещения. Этот подход чем-то аналогичен чтению книги в сравнении с чтением компьютерной распечатки. Распечатка заставляет вас делать последовательный просмотр, а книга допускает пря- мой доступ (и обычно предоставляет оглавление и предметный указатель для поиска специфических пунктов). Ctags преодолевает этот разрыв, создавая файл специального форма- та, который распознают редакторы vi и ex. Этот файл содержит "призна- ки", которые могут быть использованы при работе с редактором для обеспечения автоматического доступа к любой нужной функции, не требую- щего от вас знаний о том, в каком файле находится функция. Фактически, ctags предоставляет вам предметный указатель для груп- пы файлов с исходным кодом на языке Си. Когда вы объединяете его с ре- дактором, вы можете быстро найти любую функцию по известному вам имени и посмотреть тело функции. Это значит также, что вы можете легко копи- ровать и вставлять функции в любой исходный файл, с которым вы сейчас работаете. Если редактор не имел возможности работы с признаками или мы не построили инструментальное средство, использующее такое преимущество, то мы должны запускать grep для имени функции на наборе исходных файлов на Си (в надежде, что у нас есть подходящие файлы!), отмечать, какой файл имеет требуемую функцию, входить в этот файл редактором (вручную вводя все символы имени файла), а затем набирать символы шаблона по- иска. Это большая работа, которая может занять много времени. Благодаря использованию возможности работы с признаками, для файла признаков, из- влеченных из исходного кода, вся эта ручная работа сокращается. Это сочетание возможностей иллюстрирует то, чему не часто прида- ется значение: владельцы UNIX всегда настороженно относятся к возмож- ности использовать преимущества многочисленных средств, уже имеющихся в таких программах, как vi или ex. Зачастую от 90 до 95 процентов необхо- димых вам возможностей уже имеются, ожидая относительно простого ко- мандного файла интерпретатора shell, связывающего их вместе в мощный новый инструмент. Ctags уже существует в виде исполняемого модуля в системе Berkely (BSD) и в нынешней AT&T System V. Он происходит из системы Berkely, но поддерживается теперь в System V. Это иллюстрация взаимодействия между этими двумя источниками в мире UNIX, поскольку они взаимствуют полезные идеи друг у друга. Данное конкретное воплощение ctags является команд- ным файлом утилиты awk, имитирующим исполняемую программу из системы Berkely, а это значит, что пользователи систем XENIX и предыдущих версий AT&T могут теперь извлечь пользу от применения ctags. Еще одно преимущество версии в виде командного файла в том, что его можно легко модифицировать, чтобы обрабатывать другие особенности языка Си. Такое вы не можете делать с исполняемым модулем, если только у вас нет доро- гостоящей лицензии на исходный код. ЧТО ДЕЛАЕТ ctags? Ctags просматривает файлы с исходным кодом на Си, переданные в ко- мандной строке, и печатает список имен функций в каждом исходном файле. Имена функций имеют специальный синтаксис и должны быть именно в таком формате, иначе awk не распознает их как таковые. Эти правила заключа- ются в том, что имя функции должно находиться в начале строки, состоять из разрешенных символов и за ним должна следовать левая скобка. Пробелы в имени функции не допускаются. Вот пример модуля программы на Си, по- даваемого на рассмотрение командному файлу ctags: main() { } func1(arg1,arg2) int arg1,arg2; { } func2(arg1,arg2)int arg1,arg2; { } Результат работы ctags направляется в стандартный вывод (на эк- ран), поэтому он должен быть перенаправлен, чтобы попасть в файл. Вхо- дом для ctags является любое число имен файлов. Напомним, что если на входе имеется несколько файлов, то выход представляет собой один непре- рывный поток данных, попадающий в один файл. Если вам нужен выводной файл для каждого входного файла, то для управления ctags можно приме- нить такой командный файл с циклом: for F in *.c do ctags $F > $F.tags done Выход ctags состоит из трех полей в таком формате: признак имя_файла шаблон_поиска Реальный выход для примера программы на Си, приведенного выше, был бы таким: main /usr/russ/src/program.c /^main()$/ func1 /usr/russ/src/program.c /^func1(arg1,arg2)$/ func2 /usr/russ/src/program.c /^func2(arg1,arg2)$/ Первое поле является именем признака (которое совпадает с именем функции). Второе поле - маршрутное имя файла, содержащего данную функ- цию. Третье поле - шаблон поиска, используемый признаковыми средствами редактора для доступа к функции внутри файла (более подробно об этом - позже). Предположим, вы можете сгенерировать правильный файл признаков. Как согласовать файл признаков с редакторами таким образом, чтобы вы могли найти интересующую вас функцию? Редактор vi предоставляет много путей для этого. Первый способ - поместить имя используемого файла признаков в файл .exrc. (Файл .exrc является аналогом файла .profile для редактора ex и работает также с редактором vi, что не удивительно, так как vi построен na ex. Поскольку vi - наиболее популярный редактор системы UNIX, мы применяем его здесь для наших примеров.) Вы можете иметь файл .exrc, который выглядит примерно так: set tags=/usr/russ/mytags Впоследствии, когда вы обращаетесь к некоторому признаку, исполь- зуется данный файл признаков. Другой способ - установить файл признаков после того, как вы вошли в редактор. Чтобы посмотреть, каким является ваш файл признаков по умолчанию, введите, находясь в vi, следующее: :set tags Эта команда печатает файл признаков, о котором она знает. Для из- менения определенного в настоящий момент файла признаков, используйте синтаксис, который был в примере файла .exrc: :set tags=/usr/russ/mytags Теперь, когда редактор знает, в каком файле искать признаки, к ко- торым вы обращаетесь, давайте рассмотрим, как обращаться к признаку (т.е. к имени функции). Первый способ - объявить его в командной строке при вызове редактора. Например: $ vi -t tag Если вы уже находитесь в редакторе vi, можете применить такую ко- манду для поиска признака: :ta tag Двоеточие означает, что мы направляемся в ex, чтобы выполнить этот поиск. Мы просим ex найти указанную строку как признак, который разме- щается в текущем файле признаков. Когда этот признак найден в файле признаков, редактор vi редактирует файл с соответствующим именем, кото- рое он берет из поля 2. Это аналогично команде ":e имя_файла". Когда новый файл внесен в буфер редактора, последнее поле файла признаков используется в качестве строки шаблона поиска. Синтаксис точно такой же, как если бы вы набирали его вручную. Курсор перемещается в позицию в файле, которая соответствует строке поиска, при этом вы попадаете на интересующую вас функцию. ВЗАИМОСВЯЗЬ МЕЖДУ ex И vi Несколько отклоняясь от темы, рассмотрим два файла: /bin/ ex и /bin/vi. Небольшое исследование обнаруживает, что на самом деле это один и тот же файл. Мы можем проверить это, посмотрев на их индексные описатели файлов. Введите такую команду: $ ls -li `path ex vi` Выход показывает, что два числа в первой колонке одинаковы. 510 -rwx--x--t 5 bin bin 121412 Sep 19 1985 /bin/ex 510 -rwx--x--t 5 bin bin 121412 Sep 19 1985 /bin/vi Это число и есть индексный описатель файла (inode). Поскольку оба файла являются одним и тем же, вызов любого из них запускает один и тот же исполняемый модуль. Каким образом он знает, как вы его вызвали? Программа смотрит на строку argv[0] и видит, какое имя вы использовали для вызова файла. Затем редактор устанавливает свой интерфейс в соот- ветствии с тем, как вы его вызвали. Обратите внимание, что эта программа имеет пять связей. Как нам найти все другие имена, которыми можно вызвать vi и ex? Мы можем использовать команду системы UNIX ncheck. Эта команда воспринимает ин- дексный описатель файла и печатает все файлы, имеющие такой описатель файла . Примеры таких команд: $ ncheck -i 510 /dev/root $ ncheck -i 510 Первый синтаксис указывает команде ncheck искать файлы с inode, равным 510, только в корневой файловой системе. Ncheck ограничена по- иском в одной файловой системе. Это подкрепляет тот факт, что файлы не могут быть привязаны к различным файловым системам, поскольку каждая файловая система начинается с inode 2 и последовательно наращивается. Каждая файловая система имеет inode 510, который уникален для каждой файловой системы. Выход предыдущей команды выглядит так: dev/root: 510 /bin/edit 510 /bin/ex 510 /bin/vedit 510 /bin/vi 510 /bin/view Если файловая система не указана, как во втором примере, выполня- ется поиск по всем файловым системам, смонтированным в настоящее время. Это не слишком хорошо для нас, поскольку пять связей vi должны нахо- диться в одной и той же файловой системе. В противном случае файлы были бы связаны поперек границ файловых систем. Мы уже можем сказать, что никаких файлов редактора нет в каталоге /usr, размещенном во втором разделе диска от корневой файловой системы. ПРИМЕРЫ 1. $ ctags *.c Генерирует файл признаков для всех файлов с исходным кодом на Си в текущем каталоге. Выход направляется на экран в отсортированном порядке для каждого файла. Порядок файлов алфавитный, так как указано расшире- ние файла. Конечно, в большинстве случаев вы захотите перенаправить вы- ход ctags в файл. 2. $ ctags `Find /usr/src -name "*.c" -print` Этот синтаксис использует командную подстановку, чтобы найти все имена файлов, оканчивающиеся на .c и поместить их в командную строку. Проблема в том, что если найдено слишком много файлов, командная строка ctags может переполниться и испортить всю команду. В зависимости от серьезности переполнения, она может испортить и весь ваш процесс shell. 3. $ find /usr/src -name "*.c" -exec ctags {} \; > tags Находит все исходные файлы на Си в сегменте дерева /usr/src. Для каждого подходящего файла запускает программу ctags на этом файле. Использование такой формы записи предотвращает порчу вашей команды (о которой только что шла речь), а также запускает ctags для каждого имени файла. Весь выход помещается в файл tags для последующего использова- ния. 4. $ find /usr/src -type f -print | sort | > while read FILE > do > ctags $FILE > done >> tags Используя преимущество множественных каталогов, находит и сортиру- ет все файлы из каталога /usr/src и печатает их маршрутные имена в стандартный вывод. Затем они сортируются и поступают по конвейеру в цикл while. Цикл while используется для обслуживания сколь угодно боль- шого числа файлов без переполнения буферов. Выход цикла while добавля- ется к одному файлу - tags. Этот цикл громоздкий и медленный, но он вы- полняет свою работу. 5. $ find /usr/src -print | ctags Это неправильный способ использования ctags. Выходом команды find являются маршрутные имена. Ctags читает стандартный ввод, поскольку в командной строке нет файлов. Получается, что данные, которые читает awk, являются маршрутными именами от find, которые не имеют корректных полей для соответствия шаблонам функций. Никакого сопоставления не про- исходит. Аналогичную проблему могла бы вызвать такая командная строка: find /usr -print | wc -l Вы могли бы интерпретировать это так: "посчитать, сколько строк имеется во всех файлах каталога /usr". Но в действительности здесь ска- зано: "сколько имен файлов имеется в древовидной структуре /usr". Для подсчета общего количества строк в этих файлах нужен такой синтаксис: find /usr -exec cat {} \; | wc -l который гласит: "найти все файлы в /usr, распечатать каждый из них, затем посчитать, сколько строк в них имеется". Чтобы так же посту- пить с ctags, нужен такой синтаксис: find /usr/src -name "*.c" -exec cat {} \; | ctags В отличие от результата, который мы получили бы в предыдущих при- мерах: func1 /usr/russ/src/program.c /^func1(arg1,arg2)$/ func2 /usr/russ/src/program.c /^func2(arg1,arg2)$/ теперь выход будет выглядеть так: func1 - /^func1(arg1,arg2)$/ func2 - /^func2(arg1,arg2)$/ ПОЯСНЕНИЯ Символы "-" вместо имени файла появляются из-за того, что ctags читает из стандартного ввода. Awk автоматически присваивает своей внут- ренней переменной FILENAME значение "-", так как знает, что в командной строке не было файлов. Весь командный файл есть программа awk. Входом для командного фай- ла утилиты awk является $@, что представляет все позиционные параметры. Каждый параметр считается именем исходного файла на Си. Если никакие файлы не передаются в командной строке, awk ищет данные в стандартном входном потоке, но это создает некорректный выход, так как переменная FILENAME в awk имеет по умолчанию значение "-". Поскольку awk требует имена файлов, мы должны вызывать ctags с именами файлов, а не переда- вать ему данные по конвейеру через стандартный ввод, как показано в предыдущем примере. Awk читает каждый раз одну строку из файла данных и сверяет ее с шаблоном, пытаясь установить соответствие. Для каждой строки, соот- ветствующей шаблону, awk выполняет заданную программу. Первое, что де- лает ctags,- изменяет подразумеваемый разделитель полей утилиты awk с пробела на левую скобку. Благодаря использованию этого символа в ка- честве разделителя полей, строка определения функции разбивается на два поля: имя функции и остаток строки. Шаблон поиска утилиты awk соответствует синтаксису имени Си-функ- ции. Оно может начинаться с символов a-z, A-Z или символа подчеркива- ния. Далее в имени могут быть любые символы из набора a-z, A-Z, 0-9 и _. Между именем и скобкой нельзя использовать пробелы. Поиск начинается от начала строки (^), за которым следует последовательность допустимых символов (a-z, A-Z, 0-9), а затем левая скобка. Когда строка соответствует данному шаблону, генерируется выход с помощью оператора printf. Первое поле - строка, представленная обозна- чением $1. В данном случае $1 - это только имя функции, исключая левую скобку. Печатается символ табуляции, затем следующая строка, которая является переменной FILENAME из утилиты awk. Эта переменная должна быть получена из командной строки, иначе awk не будет знать имя файла, в ко- тором размещена данная функция, и файл признаков потеряет информацию, необходимую для доступа к файлу, содержащему функцию. Печатается еще одна табуляция, затем строка поиска. Строкой поиска является $0, что представляет всю строку, с которой работает awk. Строке предшествует символ ^, а за строкой следует символ $. Выход пропускается по конвейеру через sort с той целью, чтобы все признаки шли в отсортированном порядке. Опции сортировки указывают ути- лите sort проверять только первое поле и печатать только одно появление строки, если имеется несколько записей. МОДИФИКАЦИИ ДЛЯ ctags Теперь, когда мы знакомы с общим форматом редактируемого файла признаков, можем ли мы применить его для других полезных целей? Мы зна- ем, что мы можем идентифицировать регулярные структуры в программах на Сии создать признаки, с помощью которых можно получить доступ к этим структурам в редакторе. В программах на Си имеются не только имена функций, но и другие интересные конструкции, например имена структур. Их можно обслуживать с помощью модифицированной версии ctags. Единственное, что нам нужно знать,- это официальный синтаксис структуры данных. Структура данных, которая нас бы заинтересовала, часто имеет такой формат: struct name { int val1; char val2; }; Все, что мы должны сделать,- это заставить awk искать все появле- ния определения структуры. Затем мы можем построить файл признаков, в котором признаком является имя структуры. Этот файл, видимо, будет та- ким же, как и прежде, а строка поиска будет обнаруживать определение структуры, а не имя функции. Фактически, комбинация утилиты awk, приз- наков и редактора может быть использована для любого вида информации, которую вы можете захотеть хранить в файле специального формата, напри- мер для адресов, заметок, библиографических ссылок и т.д. Вам просто нужно подобрать соответствующие разделители и правильно их использо- вать. Мы надеемся, что облегчили сопровождение ваших программ и предло- жили вам идеи для других способов автоматической обработки документа- ции. Вы можете без особого труда учреждать и поддерживать локальные соглашения о документации с помощью командных файлов, аналогичных представленным здесь. Примером проекта, за который вы можете взяться, является согласование наших программ извлечения информации (stripf, stripc, strips) и других программ, которые вы пишете, таким образом, чтобы они могли читать файл-формирователь (makefile, см. Make(1)) и вы- давать полную документацию по всем исходным файлам, участвующим в дан- ном проекте.  * ГЛАВА 5. УПРАВЛЕНИЕ ЛИЧНОЙ ИНФОРМАЦИЕЙ I: *  УПРАВЛЕНИЕ ВРЕМЕНЕМ И ДЕЛОПРОИЗВОДСТВОМ УПРАВЛЕНИЕ ВРЕМЕНЕМ at выполнение задач в указанное время b порожденный shell фоновых задач greet своевременное приветствие с терминала lastlog сообщение времени последней регистрации timelog учет и статистика сеансов работы today печать календаря с отмеченной текущей датой УПРАВЛЕНИЕ ДЕЛОПРОИЗВОДСТВОМ jargon генератор технических терминов phone база данных с телефонными номерами office делопроизводитель УПРАВЛЕНИЕ ЛИЧНОЙ ИНФОРМАЦИЕЙ I: УПРАВЛЕНИЕ ВРЕМЕНЕМ И ДЕЛОПРОИЗВОДСТВОМ ВВЕДЕНИЕ Мы уже многое знаем о файлах и о том, как управлять файловой структурой. Пора рассмотреть, как мы можем использовать систему UNIX для управления множеством задач, которые составляют наш рабочий день и держат нас в курсе того, что делают другие пользователи. Термин "уп- равление личной информацией" (personal management) подразумевает, что вы хотите создать свою собственную ПЕРСОНАЛЬНУЮ рабочую среду и инструментальные средства. Мы предлагаем вам пакет программ, которые вы можете приспособить к вашим требованиям. Фактически мы в этой и следующей главе представляем четыре отдельных набора программ, каждый из которых посвящен определенному аспекту управления личной информаци- ей. Средства управления временем помогают нам спланировать выполнение задач компьютером, а также контролировать наше личное время. Управле- ние делопроизводством имеет дело с хранением и извлечением информации, а также с организацией доступа к различным функциям системы UNIX посредством простого в использовании интерфейса в виде меню. Для каждой из этих областей деятельности мы даем ее обзор, а за- тем представляем соответствующую группу средств. УПРАВЛЕНИЕ ВРЕМЕНЕМ Поскольку система UNIX имеет встроенные функции поддержки времени и часы, она может следить за временем. Объединение функций поддержки времени с возможностью автоматического запуска группы команд означает, что мы можем настроить их так, чтобы компьютер выполнял многие наши рутинные работы, связанные со временем. Мы также можем использовать компьютер для отслеживания нашего собственного времени. В данном разделе представлены инструментальные средства at, b, greet, lastlog, timelog и today. Командный файл at дает нам возможность сказать машине о том, что в указанное время необходимо сделать то-то и то-то (вывести на экран сообщение или выполнить какие-то другие команды). Задача запускается в фоновом режиме, так что мы можем продолжать другую работу, а фоновая задача выполнится автоматически в указанное время. Эта задача может состоять из любых разрешенных в UNIX команд, поэтому ее возможности очень гибкие. Мы просто предлагаем некоторые идеи, связанные с ее использованием. Вторым средством является командный файл b. Это обработчик фоно- вых задач. Очень часто при порождении фоновых процессов мы не можем узнать, когда они закончились. Для того, чтобы это определить, нам не- обходимо вручную просмотреть таблицу процессов или найти какой-то иной признак того, что данная работа завершена. Командный файл b запускает задачу, управляет операциями ввода-вывода и затем сообщает нам о том, что задача завершена. Командный файл greet показывает, каким образом переводить внут- реннее время компьютера в более понятные пользователю категории. Он различает три перида суток (утро, день и вечер) и реагирует на них соответствующими сообщениями. Это довольно просто, но обеспечивает неплохое основание для подхода к решению других проблем, связанных со временем. Далее мы представляем два средства, которые образуют базис систе- мы управления временем. При выполнении множества работ нам необходимо подсчитать время, которое мы потратили на данный проект, чтобы мы мог- ли выставить нашему клиенту соответствующий счет. Командный файл lastlog запускается автоматически, когда вы регистрируетесь в системе. Поддерживается база данных, в которую каждый раз записывается время вашей регистрации для последующего анализа или хранения записей. С этим инструментальным средством соседствует командный файл timelog. Это утилита, которая выполняет подсчет времени. Она может следить за общим времене