ГЛАВА 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 завершается.

Обновлено: 12.03.2015