преимущество версии в виде командного файла в том, что его можно легко
модифицировать, чтобы обрабатывать другие особенности языка Си. Такое
вы не можете делать с исполняемым модулем, если только у вас нет доро-
гостоящей лицензии на исходный код.
ЧТО ДЕЛАЕТ 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 с именами файлов, а не переда-
вать ему данные по конвейеру через стандартный ввод, как показано в