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)) и выдавать полную документацию по всем исходным файлам, участвующим в данном проекте.

Назад | Содержание | Вперед