Приемы профессиональной работы в UNIX

             

Cptdir - копирование дерева каталога


ИМЯ: cptdir

cptdir Копирует дерево каталога в другое место



Autobkp - автоматически наращивамый файл резервной копии


Имя: autobkp

autobkp Автоматически наращиваемый файл резервной копии



Can - удаление файлов в "мусорную корзину"


Имя: can

can Управление "мусорной корзиной" файлов



Cpiobr - копирование и восстановление файлов в виде потока данных


ИМЯ: cpiobr

cpiobr Копирование и восстановление в виде потока данных командой cpio



Dosflp - копирование файлов с




Имя: dosflp

dosflp Копирование файлов с гибкого диска формата DOS с использованием символов шаблона в именах файлов



Dsum - контрольные суммы двух каталогов


ИМЯ: dsum

dsum Контрольная сумма двух каталогов



ФАЙЛ СО СПИСКОМ МАРШРУТОВ


Чтобы сделать интерфейс настолько гибким, насколько это возможно, autobkp читает стандартный ввод. Переназначая stdin, вы можете поддерживать разные списки файлов, которые необходимо копировать и переключать их в командной строке. Вы можете иметь один список маршрутов для системных файлов, другой для исходных файлов, третий для личных файлов, четвертый для файлов с готовым продуктом и так далее. Для каждой из этих групп файлов создается список маршрутов и передается в качестве входа для autobkp. Входные данные читаются как три поля: FROM, TO иTYPE. Поле FROM - это каталог-источник. Поиск файлов начинается с этого места. Напомним, что autobkp проходит вниз до конца дерева файлов, начиная с указанного каталога.

Поле TO - это каталог-приемник, куда все файлы, найденные для данной записи в файле со списком маршрутов, помещаются на машине-приемнике или в разделе-приемнике.

Поле TYPE - это описатель-шаблон, который сообщает autobkp, какие файлы искать. Его значение может быть *, *.c, *src*, и так далее. Как мы увидим позже, этот описатель передается команде find Unix, которая фактически и выполняет поиск файлов. Вы можете использовать любое выражение в поле TYPE, если оно соответствует синтаксису find.

Итак: все файлы, которые были изменены в последние 24 часа, обнаруживаются в списке FROM с помощью описателя TYPE и копируются в область TO.

Ниже приводится типичный файл со списком маршрутов. Он указывает несколько каталогов, в которых производится поиск файлов. Обратите внимание, что эти каталоги находятся под регистрационным каталогом: если вы хотите скопировать ВЕСЬ регистрационный каталог полностью, вы можете указать этот каталог, но здесь мы хотим выбрать только указанные каталоги.

/usr/russ/bin /pack1/russ/.bkp/bin * /usr/russ/doc /pack1/russ/.bkp/doc * /usr/russ/src /pack1/russ/.bkp/src *.c /usr/product1 /pack1/russ/.bkp/product1 *.[ch]

Эти строки копируют каталоги bin, doc и src на локальной машине автора. В случае каталога src мы указали, что копировать нужно только исходные файлы на языке Си. Будет также скопирована некоторая полезная информация из другого места этой же системы. Будут скопированы только файлы с расширением *.c и *.h.

Место назначения (прямо указанное в командном файле автоматического копирования) - другая система UNIX. Место назначения - некоторый смонтированный диск, регистрационный каталог, подкаталог копий (bkp).



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


dosflp [-a] [-c] [-dDRIV] [-eEXP][-h] [-l] [-r] [-sDIR]

где

-aозначает копирование файлов, соответствующих *.asm
-cозначает копирование файлов, соответствующих *.c
-dвыбирает имя устройства DRIV из набора A,B,X,Y (по умолчанию A)
-eиспользует выражение EXP, чтобы применить к файлам grep
-hкопирует файлы, соответствующие *.h
-lтолько выдает список файлов
-rудаляет файлы вместо их копирования
-sуказывает подкаталог DIR на гибком диске формата DOS



ФУНКЦИЯ


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



ИСХОДНЫЙ КОД ДЛЯ CAN


1 : 2 # @(#) can v1.0 Maintain file trash can Author: Russ Sage 4 CAN=$HOME/.trashcan 6 if [ ! -d $CAN ] 7 then mkdir $CAN 8 fi 10 if [ "`echo \"$1\"|cut -c1`" = "-" ] 11 then case $1 in 12 -l) echo "$CAN:" 13 ls -al $CAN 14 exit 0;; 15 -r) echo "removing $CAN/*:" 16 rm -rf $CAN/* 17 exit 0;; 18 -z|-?) echo "usage can [-l] [-r] file [file ...]" gt;&2 19 exit 0;; 20 esac 21 fi 23 mv $@ $CAN



ИСХОДНЫЙ КОД ДЛЯ DOSFLP


1 : 2 # @(#) dosflp v1.0 Wildcard copies from DOS floppy Author: Russ Sage 4 EXP=.\* 5 DRIVE="A:" 6 OP="c"   8 if [ "$#" -gt 0 ] 9 then for ARG in $@ 10 do 11 case "$ARG" in 12 -a) EXP='.*\.asm$';; 13 -c) EXP='.*\.c$';; 14 -d*) DRIVE="`echo $ARG | cut -c3-`:";; 15 -e*) EXP='`echo $ARG | cut -c3-`';; 16 -h) EXP='.*\.h$';; 17 -l) OP="l";; 18 -r) OP="r";; 19 -s*) DRIVE="$DRIVE`echo \"$ARG" | cut -c3- `/";" 20 *) echo "dosflp: arg error" 21 echo "usage: dosflp [-a] [-c] [-d] [-e] [-h] [-l] [-r] [-s]" 22 exit 1;; 23 esac 24 done 25 fi   27 case $OP in 28 c) echo "\nCopying files from $DRIVE to `pwd`";; 29 l) echo "\nListing files on $DRIVE" 30 dosdir $DRIVE | more 31 exit;; 32 r) echo "This option removes all the data on the floppy." 33 echo -n "Do you want to do this (y/n): " 34 read RSP 35 if [ "$RSP" = "y" ] 36 then echo "\nRemoving files on $DRIVE" 37 else exit 38 fi;; 39 esac   41 dosls $DRIVE | tr "[A-Z]" "[a-z]" > /tmp/doslist   43 for FILE in `grep "$EXP" /tmp/doslist` 44 do 45 echo $FILE 46 case $OP in 47 c) doscp $DRIVE$FILE .;; 48 r) dosrm $DRIVE$FILE;; 49 esac 50 done   52 rm /tmp/doslist  



ИСПОЛЬЗОВАНИЕ cron


Теперь, когда процедура autobkp знает, что искать, давайте скажем ей, когда искать. Cron, вечный резидентный хранитель времени, может легко выполнить эту работу. Входные данные для cron обычно устанавливаются системным администратором (или кем-либо, кто имеет права записи в/usr/lib/crontab), так что вы должны попросить администратора установить для вас вход в файл данных cron. Для получения дополнительной информации о входных данных cron, прочтите cron(1M) в Руководстве администратора. Коротко говоря, полями в файле /usr/lib/crontab являются минута, час, день месяца, месяц и день недели. Используя *, мы можем установить принудительно многие из этих полей во все возможные значения. Входные данные для cron, копирующие мой регистрационный каталог в4.00 утра каждый день каждой недели каждого месяца года, выглядят так:

0 4 * * * /usr/russ/bin/autobkp.cron Обратите внимание, что вход в cron вызывает управляющую процедуру вместо того, чтобы непосредственно использовать autobkp. Имеется несколько важных причин, чтобы написать процедуру на базе утилиты autobkp. Во-первых, cron не печатает диагностическую информацию на ваштерминал, поэтому если что-нибудь идет не так, вы никогда об этом не узнаете. Во-вторых, проще поддерживать усеченную версию autobkp, а звуковые предупреждения добавлять в управляющую процедуру. Вы можете сделать собственные модификации управляющей программы и не беспокоиться об отсутствии сообщений от самой утилиты. Управляющую программу можно настолько усложнить, насколько вы желаете. Представленная здесь вполне работоспособна, но легко может быть дополнена.

# Cron-driven autobkp driver echo "backed up: `date`" > /dev/tty00 /usr/bin/autobkp < /usr/russ/bin/autobkpath >> /usr/russ/bin/autobkp.log

Этот драйвер выдает сообщение на терминал, запускает autobkp, использует для ввода файл со списком маршрутов в каталоге bin и помещает все выводные сообщения в файл протокола. Отметим, что имя терминала дано как абсолютное (tty00). Это правильно только в том случае, когда в вашей системе имеется такой терминал. Использование этого имени терминала позволяет сообщению появиться на экране даже если никто на нем не зарегистрирован. Это хорошо, потому что первое, что вы сможете увидеть утром на вашем экране - это сообщение. Если у вас нет указанного терминала, вы можете сделать что-то другое, например, передачу самому себе почтового сообщения.



КОМАНДНЫЙ ФАЙЛ autobkp


1 : 2 # @(#) autobkp v1.0 Automatic file backup Author: Russ Sage 4 if [ $# -gt 1 ] 5 then echo "autobkp: argument error" >&2 6 echo "usage: autobkp [-c] [>logfile]" >&2 7 exit 8 fi   10 if [ "$1" = "-c" ] 11 then COPY=on 12 else COPY=off 13 fi   15 echo "\nBACKUP DATE `date '+%a %m/%d/%y %H:%M:%S'`" 16 echo "-----------------------------------------"   18 SYSTEM='' # destination system uucp node name 19 : ${SYSTEM:=`uuname -l`}   21 echo "Sourse system:\t\t`uuname -l`\nDestination system:\t$SYSTEM"   23 while read SRCDIR DESTDIR FILES 24 do 25 if [ ! -d $SRCDIR ] 26 then echo "autobkp: $SRCDIR is not a directory" 27 continue 28 fi   30 cd $SRCDIR 31 echo "\nFinding files in: $SRCDIR"   33 for FILE in `find . -type f -ctime 0 -name "$FILES" -print` 34 do 35 case $COPY in 36 off) uucp $FILE $SYSTEM!$DESTDIR;; 37 on) cp $FILE $DESTDIR;; 38 esac 39 echo " Transferred $FILE to $DESTDIR" 40 done 41 done  



КОМАНДНЫЙ ФАЙЛ CPIOBR


1 : 2 # @(#) cpiobr v1.0 Cpio stream backup and restore Author: Russ Sage 4 if [ "$#" -gt "0" ] 5 then echo "cpiobr: too many arguments" 6 exit 7 fi   9 while : 10 do 11 c 12 set `date` 13 echo "   15 $1, $2 $3 $4   17 Cpiobr Backup & Restore 18 ----------------------- 19 Backup to removable media 20 Restore from removable media 21 List files on media 22 Long list files on media 23 to exit   25 Press b,r,f,l or : \c"   27 read CMD 28 if [ "$CMD" = "" ] 29 then break 30 fi   32 ABORT=off   34 while : 35 do 36 echo "   38 Enter media type: 39 Raw System V floppy drive (/dev/rfp021) 40 Raw XENIX floppy drive (/dev/rfd0) 41 Tape drive (/dev/rmt0) 42 Any device (/dev/???) 43 to exit   45 Press s,x,t,a, or : \c"   47 read MEDIA 48 case $MEDIA in 49 s|S) DEV=/dev/rfp021 50 break;; 51 x|X) DEV=/dev/rfd0 52 break;; 53 t|T) DEV=/dev/rmt0 54 break;; 55 a|A) echo "enter full pathname (or <> to exit): \c" 56 read DEV 57 if [ "$DEV" = "" ] 58 then continue 59 else break 60 fi;; 61 "") ABORT=on 62 break;; 63 *) echo "cpiobr: invalid command \"$MEDIA\"";; 64 esac 65 done # while get media   67 if [ "$ABORT" = "on" ] 68 then continue 69 fi   71 case $CMD in 72 b|B) echo "\nEnter the source directory name: \c" 73 read SRC 74 cd $SRC 75 echo "\nPlace floppy in drive and hit ...\c" 76 read CMD 77 find . -print | sort | cpio -ocBv > $DEV 78 echo "\nhit \c" 79 read CMD 80 ;; 81 r|R) echo "\nEnter the destination directory name: \c" 82 read DEST 83 cd $DEST 84 echo "\nPlace floppy in drive and hit ...\c" 85 read CMD 86 cpio -icBvdmu < $DEV 87 echo "\nhit \c" 88 read CMD 89 ;; 90 f|F) cpio -icBt < $DEV 91 echo "\nhit \c" 92 read CMD 93 ;; 94 l|L) cpio -icBtv < $DEV 95 echo "\nhit \c" 96 read CMD 97 ;; 98 *) echo "cpiobr: invalid command \"$CMD\"" 99 ;; 100 esac 101 done



КОМАНДНЫЙ ФАЙЛ CPTDIR


1 : 2 # &(#) cptdir v.1.0 Copy a directory tree Autor: Russ Sage 4 if [ $# -lt 2 -o $# -gt 3 ] 5 then echo "cptdir: argument error" >&2 6 echo "usage: cptdir [-s] srcdir desdir" >&2 7 echo " -s silent mode" >&2 8 exit 1 9 fi 11 if [ "$1" ="-s" ] 12 then OPT="-pd" 13 shift 14 else OPT="-pdv" 15 fi 17 SRC=$1 18 DEST=$2 19 umask 0 21 if [ -d $DEST ] 22 then echo "\"$DEST\" already exist. Remove it? (y/n): \c" 23 read CMD 24 if [ "$CMD" = "y" ] 25 then rm -rf $DEST 26 mkdir $DEST 27 fi 28 else mkdir $DEST 29 fi 31 if [ "`echo $DEST|cut -c1`" = "/" ] 32 then cd $SRC 33 find . -print | sort | cpio $OPT $DEST 34 else PWD=`pwd` 35 cd $SRC 36 find . -print | sort | cpio $OPT $PWD/$DEST 37 fi



КОМАНДНЫЙ ФАЙЛ 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



КОМАНДНЫЙ ФАЙЛ 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



Log - меню доступа к файлам протокола копирования


ИМЯ : log

log Меню доступа к файлам протокола копирования



НАЗНАЧЕНИЕ


Перемещает файлы в "мусорную корзину", симулируя их удаление. Это допускает восстановление файлов после их кажущегося удаления.


Копирует файлы с гибкого диска в формате DOS (в XENIX) на жесткий диск. Обеспечивает возможность использования записи с помощью символов-шаблонов для имен файлов на гибком диске, где такая запись обычно недопустима.




Производит поиск по дереву файлов, которые изменялись за последние24 часа, и пересылает их в другую систему (посредством uucp) или перемещает их в другую область жесткого диска.




Обеспечивает интерфейс в виде меню с командой cpio и удобства при копировании и восстановлении файлов. Выходные данные на носитель копируются в виде потока данных.




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




Обеспечивает интерфейс в виде меню к файлам протокола, полученным от утилиты autobkp.



НЕКОТОРЫЕ ОСОБЕННОСТИ uucp


Когда используется uucp, в маршруте приемника должен быть установлен бит разрешения выполнения ("x") для группы "others" (остальные) для всех промежуточных каталогов, ведущих к файлу. Это будет выглядеть так:

--------x

Самый последний каталог должен иметь права доступа вида "wx", чтобы uucp могла писать файл в каталог. После этого владельцем файла считается uucp. Если собственником файла хотите быть вы, скопируйте его (используя cp, а не mv) с другим именем и он будет вашей собственностью. Если вы переименуете его командой mv, вы только измените имя, связанное с тем же индексным описателем файла (inode). Но если вы скопируете его командой cp, вы создадите новый отмеченный описатель файла. Этот новый описатель файла (созданный вами) имеет ваши идентификатор пользователя (uid) и идентификатор группы (gid), поэтому вы владеете им. Если вы находитесь в корне системы и копируете файл (используя cp, а не mv) поверх другого существующего файла, информация в описателе файла не изменяется, а меняются только данные, доступ к которым указывает описатель файла.

Когда uucp устанавливает предшествующие права доступа к файлу на всех промежуточных каталогах такими, что все имеют право записи, последний каталог НЕ будет иметь защиты. Предоставление любому пользователю права записи означает, что кто угодно может удалить или изменить файлы в этом каталоге. Не каждый хочет давать всем это право. Если же вы копируете файлы в обычную область команды uucp общего доступа (/usr/spool/uucppublic/$LOGNAME), то вы должны внимательно следить за ними. Многие системы имеют запускаемые с помощью cron программы, производящие в данном каталоге поиск файлов, к которым не было доступа в течение определенного количества дней, и удаляют такие файлы - это вредит вашим копиям. Если период хранения больше, чем промежуток между вашим копированием, у вас может быть все в порядке. Как и многое другое, это зависит от ваших обстоятельств и требований безопасности.



ОПЕРАЦИИ СОПРОВОЖДЕНИЯ


Сопровождение файлов включает два вида операций: создание резервных копий (копирование) и удаление "мусора".

Копирование - это дань уважения, которую мы платим за хрупкость физических данных в руки Мерфи и других богов энтропии. Хорошее средство копирования является быстрым, гибким, простым в использовании и стимулирует пользователей часто копировать самые важные файлы. В последующем тексте будут представлены различные методы копирования, пригодные для разных конфигураций системы и типов носителей.

Имеется два вида резервных копий: "мягкие" и "твердые". "Мягкие" резервные копии - это копии в другом файле или каталоге в той же или в другой файловой системе (т.е. разделе) на том же или другом жестком диске. Такого рода копирование сделать легко и оно предохраняет от наносимого самому себе ущерба, такого как удаление файла по невнимательности. Чаще всего для такого типа копирования используется наше средство cptdir. Основной недостаток мягкого копирования заключается в том, что вы по-прежнему уязвимы для таких воздействий, которые влияют на ваш физический носитель (обычно жесткий диск) так, что и оригинал и копия оказываются разрушенными.

"Твердая" копия - это копия на другом устройстве или даже в другой системе UNIX. Средства, представленные ниже в данной главе, управляют такого рода копированием и дают вам возможность выполнять копирование такого типа и с такой периодичностью, которые соответствуют объему вашей вычислительной системы, уровню ее активности и важности хранимых данных.

Твердое копирование всегда несколько утомительно, потому что диски или ленты должны быть смонтированы (или должна быть установлена связь с другой системой), а эта операция требует много времени. Преимущество, естественно, заключается в том, что вы больше не зависите от целостности какого-либо одного устройства.

Автоматизируя нашу процедуру копирования, мы стараемся сделать его как можно менее болезненным. Делая наши средства копирования в какой-то степени разумными, мы можем выбрать только файлы, которые нуждаются в копировании, и тем самым сохранить время и память. Наилучший способ обеспечить, чтобы копирование выполнялось регулярно - минимизировать время и требуемые для этого усилия. Наконец, создание процедур для проверки правильности копий даст вам спокойствие духа.

"Удаление мусора" можно автоматизировать путем указания и подготовки к удалению файлов, которые, вероятно, будут временными, либо каких-то других файлов, которые созданы (но не обязательно разрушены) при компиляции, выполнении конвейеров или другими операциями. Вы также можете указывать файлы, специфичные для ваших работ как не подлежащие удалению.



По большому счету система UNIX,


Зачем нам нужен can?
По большому счету система UNIX, при всем ее великолепии, является просто структурой для накопления и манипулирования данными в файлах. Как мы отмечали раньше, эта система включает сотни файлов. Некоторые файлы вы желаете хранить неопределенно долго, в то время как другие отслужили свое и создают беспорядок на диске. К несчастью, легко выбросить то, что в действительности вы хотели сохранить. Команду rm совершенно не украшает то, что она является печью для сжигания мусора: бросьте что-нибудь в нее и оно пропадет (если только вы не имеете копии, а восстановление копии - это трудоемкая работа). Вот несколько классических примеров неверного применения команды rm:
rm * /tmp <-- Удалить все файлы в каталоге /tmp
Мы хотели сказать rm /tmp/*, а на самом деле произошло сначала удаление всех файлов в текущем каталоге, а затем попытка удалить /tmp. Последнее будет безуспешным, поскольку tmp - это каталог. В результате мы удалили все, что хотели сохранить, и сохранили все, что хотели удалить! Этот синтаксис похож на другие операторы UNIX, вроде "grep *file": противная ошибка.
rm -rf / tmp <-- Удалить каталог tmp со всеми файлами
Мы хотели сказать rm -rf /tmp, но нечаянно вставили пробел в команду. На самом деле удалятся ВСЕ файлы во всей системе (если мы дадим команде выполняться достаточно долго), потому что мы сказали UNIX удалить корневой каталог и всех его потомков! Вы должны быть внимательны с командой rm. Если покажется, что что-то не так, удалите эту команду. Она может погубить вас.
Одна такая ошибка может испортить вам целый день. После того, как это случится, вы станете осторожным на некоторое время, потом внимание ослабнет. Если вы не будете бдительным, ошибки вернутся, чтобы преследовать вас.
Для нас "мусорная корзина" более желательна, чем печь для сжигания "мусора". Используя этот путь, вы можете вернуться и восстановить то, что вы выбросили по ошибке. Вы также хотели бы контролировать, когда появится мусоро сборщик, захватит и окончательно удалит "мусор". Вы можете периодически просматривать содержимое "мусорной корзины", а затем очищать корзину, когда вы уверены, что вы не хотите ничего в ней сохранять. Нельзя допускать, чтобы корзина была слишком заполнена, потому что она занимает дисковое пространство.
Что делает can?
Командный файл can предназначен для управления "мусорной корзиной"ваших файлов. Используя утилиту, вы можете свести к минимуму случайныепотери во время работы и даже впоследствии восстанавливать файлы принеобходимости.
Can не только помещает ваши файлы в "мусорную корзину", но и показывает вам, что в ней в настоящее время находится и очищает ее, когда вы этого хотите.
Can распознает только ключи -l и -r. Ключ -l показывает, что находится в "мусорной корзине", а -r удаляет все ее содержимое. Запомните, что если вы что-то удалили из "мусорной корзины", вы не сможете его восстановить.
Процесс помещения файлов в "мусорную корзину" выполняется командой mv. Ключи, предназначенные для can, должны быть первым аргументам в командной строке. Если вы желаете передать ключи команде mv, то их можно поместить в любом месте командной строки. Единственные ключи, дающие синтаксическую подсказку, - это -z и -?. Их предназначение - быть флагами только для обработки ошибок. Благодаря наличию специальных флагов обработки ошибок, выдающих справочную (help) информацию, ключи команды mv, как и ключи can, можно помещать первыми в командной строке, не оказывая влияния на can. Если вы создаете ваши командные файлы так, чтобы эти ключи всегда выдавали информацию об использовании (т.е. никогда небыли "настоящими" ключами), то вы имеете хороший способ получения помощи по синтаксису. Многие (но, увы, не все) стандартные команды UNIX дают по ключам -z или -? подсказку об использовании и это полезно помнить всякий раз, когда вы попали в тупик.
Если can не получает никаких ключей, действие по умолчанию заключается в пересылке всех указанных файлов в "мусорную корзину", размещенную в вашем регистрационном каталоге под именем $HOME/.trashcan. Если этот каталог отсутствует, он автоматически создается при первом выполнении командного файла can. Это позволяет вам запускать команду, не указывая специального положения "корзины". Если вы применяете ключ-r, файлы в "мусорной корзине" будут удалены, а сама она нет.


Зачем нам нужен cptdir?
Мы уже отмечали необходимость в дополнительных командах, которые рекурсивно обходят древовидную структуру файловой системы UNIX. В ранних версиях UNIX единственная команда tar могла управлять движением по дереву. В более новых версиях системы имеется опция -r в команде cp, которая делает cp рекурсивной (эта возможность реализована только в последней версии System V) и команда cpio. Последняя является многоцелевой командой копирования, которая может иметь дело как с потоковым форматом, так и с форматом файловой системы.
Проблема при использовании даже таких улучшенных стандартных команд системы UNIX состоит в том, что вам необходимо указать множество деталей и убедиться в том, что вы правильно используете синтаксис.
Ошибки могут привести к потере времени и даже хуже того, к неожиданным побочным эффектам. С некоторыми из этих эффектов связаны изменения прав доступа и владельца, порядок распределения индексных дескрипторов файлов (inode), размещения файлов-приемников и результирующие полные имена. Очень много необходимо запомнить и заново вызывать каждый раз при копировании. Поскольку такое копирование делается не часто, тяжело запомнить все эти детали. Мы разрешаем эту проблему, автоматизируя детали процесса и в то же время предоставляя пользователю гибкость и управление результатами. Мы создаем инструменты для управления файлами, которые являются хорошими дополнительными средствами к основным командам системы UNIX.
Что делает cptdir?
Процедура cptdir копирует каталог (и все дерево под ним, если оно существует) в другой каталог системы. Поскольку каталоги предусматривают логический доступ и не являются аппаратно-зависимыми (в отличие от имен устройств), то вы можете легко копировать файлы в другое место на том же диске или копировать их на другой диск полностью без специального синтаксиса или опций.
Вы можете указать, хотите ли вы, чтобы на экран выводились имена копируемых файлов. Если вы не хотите этого, используйте опцию -s ("silent" - молчаливый). По умолчанию используется режим "verbose" (многословный), который отображает имена по мере копирования файлов.
Заметьте, что это копирование, а не перемещение файлов. Недостаток копирования в отличие от перемещения заключается в том, что если приемником является каталог на том же диске, то вам требуется дополнительное место на диске для размещения второго образа. Вам также необходимо иметь достаточно описателей файлов (inodes) для сохранения всех файлов. В противном случае вы можете лишиться шанса сбросить в "мусорную корзину" ваши рабочие файлы.
В командной строке допустимо указание каталога-источника и имя каталога-приемника. Единственный ключ, допустимый в командной строке это "-s". Любой другой ключ приводит к завершению команды, не вызывая никаких разрушений. Вы, конечно, можете добавить программный код с целью проверки опции и выдачи сообщения о допустимых ключах, если указано нечто отличное от -s. Если вы делаете еще какую-либо проверку на наличие ошибок сверх того, что требуется для предотвращения разрушения данных или системы, то это дело личного вкуса. Минимизация проверок на наличие ошибок дает более компактные и быстрые сценарии, подходящие для опытных пользователей.
Если указанный каталог-приемник не существует, то он создается. Если каталог-приемник уже существует, выдается сообщение об этом и вам задается вопрос о том, хотите ли вы очистить его. Если вы ответите "yes", каталог уничтожается и создается снова пустым. Если вы ответите "no", каталог остается таким, какой есть и копируемые файлы просто добавляются к уже существующим в наличии. При этом может возникнуть некоторая путаница, особенно если некоторые файлы с такими именами уже существуют в каталоге-приемнике. В большинстве случаев, однако, у пользователей не появляется желания добавлять свою копию в существующий каталог.
Тем не менее каталог-приемник должен быть создан, поскольку необходимо его наличие, чтобы команда cpio работала правильно. Если же его нет, cpio не выполнится и выдаст сообщение об ошибке.
Процедура cptdir начинает копирование путем прохождения по каталогу-источнику и формирования списка файлов, находящихся в нем, рекурсивно обходя дерево сверху вниз. В результате может получиться, что скопируется больше, чем вы планировали, поэтому вам необходимо знать размер файловой структуры, которую вы хотите скопировать. Затем файлы копируются в каталог-приемник. Исходные файлы никак не модифицируются и не изменяются (за исключением того, что дата последнего доступа может быть модифицирована).
Когда идет копирование, на экран выдается сообщение от cpio, которое показывает полный маршрут к файлам-приемникам. Этот маршрут должен соответствовать маршруту, указанному в командной строке, в противном случае что-то не так.

ПЕРЕМЕННЫЕ СРЕДЫ


CMD Команда, полученная от пользователя

DEST

Каталог-приемник, в который нужно копировать OPT

Опции, которые передаются утилите cpio PWD

Текущий рабочий каталог SRC Каталог-источник, из которого нужно копировать

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


CAN Положение каталога "мусорной корзины"
HOME Положение вашего регистрационного каталога


ARG Хранит аргументы командной строки
DRIVE Устройство с гибким диском формата DOS
EXP Выражение, имитирующее действие символа-шаблона
FILE Хранит имя файла, над которым производится действие
OP Ключ, определяющий необходимое действие




COPY Флаг, определяющий, используется команда uucp или cp
FILE Имя каждого файла, найденного в исходном списке маршрутов
FILES Символ-шаблон, указывающий, какие файлы определены
PATH1 Имя маршрута-источника
PATH2 Имя маршрута-приемника
SYSTEM Имя системы-приемника для uucp




ABORT Флаг, определяющий, делать ли аварийное прекращение
CMD Команда, получаемая от пользователя
DEST Каталог-приемник при восстановлении
DEV Маршрутное имя устройства носителя
MEDIA Хранит тип устройства, которое будет использоваться
SRC Каталог-источник при копировании




BASEF Содержит базовое имя файла из полного маршрутного имени
FILE Содержит имя каждого проверяемого файла
FLIST Содержит указание на тип проверяемых файлов




CMD Команда, полученная от пользователя
HOME

Ваш регистрационный каталог в системе



ПОЯСНЕНИЯ


Строка 4 устанавливает место "мусорной корзины" так, чтобы она размещалась в вашем регистрационном каталоге под именем .trashcan. Заметьте, что ее именование, начиная с точки, делает ее не распечатываемым, или скрытым файлом. Единственный способ увидеть такие файлы использовать ключ -a в команде ls.

Строки 6-8 проверяют, определен ли сейчас каталог "мусорной корзины". Если нет, он создается. Обратите внимание, что поскольку его создаете вы, он имеет такие права доступа на чтение и запись, как в вашем регистрационном каталоге. Строки 10-21 проверяют, начинается ли первый позиционный параметр с черточки (-). Если такой параметр обнаружен, проверяется, является ли он ключом командного файла can (-l, -r, -z или -?). Обратите внимание, что для того, чтобы для использования двойных кавычек внутри двойных кавычек (строка 10), вы должны экранировать кавычки. Символ обратной косой черты (\) использован именно для этой цели.

Если указан ключ -l, выдается напоминание об имени каталога "мусорной корзины", команда ls выводит список файлов в "мусорной корзине" и процедура can завершается, поскольку требовалось только вывести список.

Если указан ключ -r, выдается сообщение об имени каталога очищаемой "мусорной корзины" и файлы в ней удаляются командой rm. Это разрушительная вещь и удаляет ваши файлы навсегда. После удаления can завершает работу. Вы можете дополнить программу процедуры так, чтобы давать подтверждение перед выполнением команды, если это позволит вам чувствовать себя более спокойно.

Если указан ключ -z или -?, выдается подсказка об использовании и can завершается. Это не совсем хорошо, но мы не можем использовать символ *, соответствующий любому другому ключу, поскольку ключ может быть предназначен для команды mv, а не для can. Благодаря использованию всего двух аргументов для обработки ошибок, мы можем разрешить передачу всех остальных аргументов. Если ключ не является одним из ключей can, или одним из указанных ключей обработки ошибок, то он передается команде mv. Если ключ недопустим для этой команды, команда mv выдает свое сообщение об ошибке и завершает работу. Вы можете, естественно, модифицировать командный файл так, чтобы он проверял допустимость ключей команды mv на "внешнем" уровне. Тогда он может выдать сообщение об ошибке и завершиться, если указанный ключ недопустим ни для can, ни для mv. Вопрос в том, стоит ли платить за более полный контроль над обработкой ошибок ценой разбухания программы и временем исполнения. Строка 23 выполняет собственно перемещение файлов в "мусорную корзину". Заметьте, что это выполняется только если не указаны никакие ключи can, поскольку это поведение can, принятое по умолчанию. Здесь используется параметр $@. Путем включения всех параметров в командную строку, любые ключи, предназначенные команде mv, передаются ей. Таким способом мы можем изменить путь, которым файлы посылаются в "мусорную корзину".


Строки 4-6 выполняют инициализацию по умолчанию путем сохранения значений в соответствующих переменных командного процессора. По умолчанию символ-шаблон ставится в соответствие всем файлам, указанным выражением для команды grep .\*. Обратная косая черта требуется для экранирования звездочки, поэтому она не перехватывается командным процессором. Устройство по умолчанию - A:. Операция по умолчанию - копировать файлы, что указано значением "c" для переменной опции.

В строках 8-25 устанавливаются значения ключей и производится проверка на наличие ошибок. Если командная строка имеет некоторые аргументы ($# -gt 0), мы перебираем каждый аргумент и проверяем его. Если найден допустимый ключ, переменные устанавливаются согласно ключу. Если обнаружен недопустимый ключ, выдается сообщение об ошибке и программа завершается с плохим статусом возврата.

Имеется два важных типа ключей. Ключи, которые выполняют прямое указание типа файла, просто устанавливают переменную EXP в соответствии с ключом. Аналогично, ключи, которые определяют, какой вид работы будет выполняться процедурой, просто устанавливают соответствующую переменную OP. Другие ключи должны обрабатываться путем извлечения одного или нескольких символов из командной строки, которые следуют за флагом ключа, эхо-отображения и конвейерной пересылки текущего аргумента ARG команде cut для извлечения символа (символов), начинающихся с третьего символа аргумента, затем присвоения результата этой операции соответствующей переменной.

Из всего сделанного следует вывод, что пробелы между ключами и символами, которые стоят за ними, не допускаются. Например, ключ -d должен получить имя устройства. По синтаксису должно быть -dB:, но не-d B:, потому что B: интерпретировалось бы как другой аргумент ARG вцикле for, а это все испортит.

В строках 27-39 операция, которая должна быть выполнена, определяется при помощи следующего оператора case. Если должно быть выполнено копирование, выдается сообщение "copying" и выполняется то, что следует за оператором case. Если должен быть выдан список файлов, выдается сообщение об устройстве, содержимое которого должно распечататься, затем выдается список файлов путем выполнения команды dosdir и конвейерной пересылки результата команде more, после чего dosflp завершается.

Если файлы должны быть удалены, пользователю выдается запрос на подтверждение удаления. Если ответ "yes", выдается сообщение, с какого устройства файлы будут удалены. Если ответ "no", dosflp завершается.

Остаток командного файла имеет дело с механизмом копирования. Строка 41 - это первый шаг в наведении моста над пропастью между двумя типами носителей. Команда dosls использована для получения полного списка файлов с гибкого диска. Перед тем как мы передадим этот список во временный файл, мы пропустим его через команду tr (translate), которая преобразует все символы на нижний регистр, чтобы при копировании файлов их имена были в нижнем регистре. В результате копии будут помещены на диск XENIX с именами файлов в нижнем регистре. Если у вас есть файлы с именами в верхнем регистре или в смеси регистров, вы должны вручную исправить их после копирования.

Строки 43-50 выполняют само копирование. Цикл for запускается для доступа к каждому файлу индивидуально. Это требование команд вида dosxx. Вы должны получать доступ к одному файлу один раз, поскольку этот уровень не обладает возможностью указания символа-шаблона. Имена файлов, которые использует цикл for, определены путем использования команды grep для выбора имен соответственно выражению, установленному ранее.

Имя каждого выбранного файла сначала отображается, так что пользователь может видеть, выполняется ли команда так, как ожидалось. В этом месте мы можем сделать одну из двух вещей: или копировать файлы, или удалить их. Эта операция определяется оператором case в строках 46-49.Если операция - копирование файлов, файлы копируются из комбинации устройство-файл в текущий каталог. Обратите внимание, что в переменную DRIVE включается подкаталог, если он был указан в командной строке. Это объясняет наличие символа "/" в конце присвоения значения переменной DRIVE в строке 16. Полное выражение должно быть таким: B:/subdir/file. Если операция - удаление файлов, комбинация устройство/файл удаляется выполнением команды dosrm. Попутно заметим, что маршрутное имя есть нечто гибкое (или небрежное, в зависимости от того, как вы смотрите на него) в том смысле, что вы можете сказать A:/subdir или A:subdir. Оба варианта правильны. После того как все файлы будут обработаны, временный файл удаляется.




Строки 4-8 выполняют проверку на наличие ошибок. Autobkp може тбыть вызван либо без указания опций, либо с одной опцией (-c, при использовании cp). Вспомните, что переназначение ввода-вывода НЕ принимается во внимание при рассмотрении аргументов, потому что командный процессор интерпретирует символы переназначения и то, что следует за ними, до вызова команды. Таким образом, если количество позиционных параметров больше одного (#1 -gt 1), получаем ошибочное условие. Затем выдается сообщение об ошибке и синтаксическая подсказка и программа завершается.

В строках 10-13 проверяется использование ключа -c. Обратите внимание, что мы не проверяем, равен ли параметр $# единице и не пытаемся выделить первый символ, чтобы посмотреть, равен ли он "-". Это потому, что такая проверка приведет к ошибке, если не указан никакой ключ (что является верным синтаксисом, как указывалось ранее).

Если мы сказали

if [ $1 = -c ]

и не указали ключей, то команда проверки не сработает и будет выдано сообщение о том, что "no argument in the statement" ("в операторе нет аргументов"). Но если мы выполним экранирование, например, так:

if [ "$1" = "-c" ]

то кавычки допускают нулевое значение аргумента, так что проверка правильно оценит недостающее значение $1 как "равен ли нуль -c?" Это даст результат "ложь", поэтому все хорошо.

Попутно давайте внимательно рассмотрим работу команды проверки. Вы можете выполнить проверку значения двумя способами. Первый - сравнение строк, а второй - числовое сравнение. Переменные командного процессора ВСЕГДА хранятся в виде строк. Вы можете, тем не менее, заставить систему рассматривать эти последовательности как числа и интерпретировать их значения как числовые, подобно оператору number = val(STRING$) языка Бейсик. Вы можете сказать системе, чтобы она изменила свой способ рассмотрения символьных строк путем изменения синтаксиса операции сравнения. Для символьных строк сравнение выглядит так:



После запуска программы выводится меню.


$ log 1
После запуска программы выводится меню. Введите число 1 для того, чтобы увидеть все имена log-файлов.
Теперь, когда мы изучили, как распознавать и управлять файлами вообще, давайте рассмотрим некоторые систематические методы управления ИНФОРМАЦИЕЙ в файлах. Мы начинаем в следующей главе с файлов, которые важны для нас как для программистов.


cptdir $HOME /bkp
Копирует каждый файл из $HOME в каталог /bkp.


dosflp Копирование всех файлов с устройства A: в текущий каталог


autobkp < filelist >> bkplog
Копирует все файлы, указанные в filelist, и записывает имена файлов в файл с именем bkplog


cpiobr Вызывает главное меню для копирования, восстановления или выдачи списка файлов


dsum $HOME/bin /mnt
Просматривает, были ли какие- либо файлы изменены при копировании из моего регистрационного каталога на гибкий диск, смонтированный в каталоге /mnt.

Перемещает все файлы, которые оканчиваются


1. $ can *.c
Перемещает все файлы, которые оканчиваются на .c, в "мусорную корзину".
2. $ can -l
Выдает список всех файлов, размещенных сейчас в "мусорной корзине".
3. $ can -r
Удаляет все файлы из "мусорной корзины".
4. $ can -q *
Передает ключ -q команде mv. Поскольку это недопустимый ключ команды mv, она выдает сообщение об ошибке и завершается.


1. $ autobkp
Запускает программу без передачи ей файла со списком маршрутов и без файла протокола. Поскольку поля FROM, TO, TYPE ищутся в стандартном вводе, введите их вручную. Когда вы нажмете возврат каретки, autobkp выполнит указанные действия, напечатает информацию на экран терминала и будет ожидать дальнейшего ввода. Для завершения выполнения командного файла введите ^d (в результате оператор read вернется с ненулевым статусом).
2. $ autobkp < pathlist
Получает все входные данные из файла со списком маршрутов, но печатает всю протокольную информацию на экран терминала. Autobkp завершается, когда прочитает все данные в файле pathlist.
3. $ autobkp >> logfile
Как и в первом случае, списки маршрутов должны быть введены с клавиатуры. Все выходные данные выводятся в файл протокола, а не на экран. Для завершения autobkp введите ^d.
4. $ autobkp -c < pathlist >> logfile
Копирует файлы из одной области жесткого диска в другую (определенную каталогом-приемником в файле pathlist). Берет все входные данные из файла pathlist и выводит все выходные данные в файл logfile.


( Здесь приводятся ответы на запросы главного меню, подменю и дополнительная информация, появляющиеся в таком порядке.)
1. b x $HOME
Копирует файлы на гибкий диск системы XENIX, начиная с каталога $HOME.
2. r a /dev/rmt0 $HOME
Восстанавливает файлы с устройства, выбранного мной (/dev/rmt0, магнитная лента), и помещает файлы в мой регистрационный каталог.
3. l s
Выдает в широком формате информацию обо всех файлах, размещенных на гибких дисках системы UNIX машины типа PC.


1. $ mount /dev/fd0 /mnt $ cp /usr/include/* /mnt $ dsum /usr/include /mnt
Монтирует гибкий диск в каталог /mnt. Копирует все файлы заголовков в каталоге /usr/ include на гибкий диск. Проверяет копии, используя dsum для исходного каталога и для каталога с копией.
Примечание: Указывая копировать *, мы вообще не попадем в каталог /usr/include/sys.
2. $ dsum . ..
Используя в качестве управляющих файлов файлы в текущем каталоге, сверить каждый файл с одноименным файлом в родительском каталоге.

СОПРОВОЖДЕНИЕ ФАЙЛОВ


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

Сопровождение файлов означает избавление от файлов, которые нам больше не нужны и в то же время систематическое копирование тех файлов, которые мы хотим сохранить. Для этого требуется возможность использования разнородных доступных носителей данных. Сопровождение файлов подразумевает также ряд систематических, повторяющихся задач, а это означает, что мы можем создать средства системы UNIX для автоматизации этого процесса.



СРЕДСТВА КОПИРОВАНИЯ


Далее представляется "рабочая лошадка" - средства копирования. Autobkp использует список маршрутных имен, чтобы определить, какие части файловой системы должны быть проверены. Затем эта программа копирует из выбранных областей те файлы, которые были добавлены или изменены в последние 24 часа.

Cpiobr предоставляет интерактивное дополнение к команде cpio системы UNIX. Она позволяет вам скопировать файлы с жесткого диска на гибкий и, если необходимо, восстановить их с гибкого диска на жесткий.



СРЕДСТВА ПЕРЕСЫЛКИ ФАЙЛОВ


Первая группа средств - это простые универсальные переносчики файлов. Программа cptdir может копировать каталог (и любые подчиненные каталоги, лежащие ниже в дереве) в каталог-приемник. Каталог-приемник это обычно каталог, назначенный в качестве резервной копии для некоторого проекта. Программа can берет на себя необходимую рутинную работу - убирает "мусор". Эта программа позволяет вам выбрать типы временных файлов, которые должны периодически удаляться. Направляя их в "мусорный" каталог, can предоставляет вам возможность просмотреть все, что было удалено, и восстановить то, что вы на самом деле хотите сохранить.

Программа dosflp допускает применение символов-шаблонов в именах файлов, используемых при копировании отобранных файлов с дискет формата MS-DOS в XENIX. Это упрощает операцию копирования и уменьшает число нажатий на клавиши.



СРЕДСТВА ПРОВЕРКИ ОПЕРАЦИЙ КОПИРОВАНИЯ


Выполнение копирования не избавит вас от волнений, пока вы не будете знать, что вы скопировали все, что хотели, и что копирование прошло корректно. Программа dsum использует контрольную сумму для проверки того, что исходный каталог и каталог-копия содержат одни и те же файлы.

Программа log отображает регистрационный файл, чтобы показать, какое автоматическое копирование выполнялось в четыре часа утра, когда вы (надеемся) спали.

При создании этих средств мы просмотрим некоторые важные команды системы UNIX и обнаружим новые способы их использования. Когда вы проработаете всю данную главу, вы будете знать, как работать "с мельчайшими частицами" при использовании файловой системы UNIX. Вы сможете автоматизировать иную большую область ваших компьютерных будней. Вы должны суметь создать пользовательские утилиты копирования и верификации, удовлетворяющие вашим нуждам. Вы сможете перевести вашу систему в режим работы, обеспечивающий ей самостоятельное выживание. (Мы оставляем философам определять, даст ли это вашему компьютеру примитивную форму жизни!)

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



УСОВЕРШЕНСТВОВАНИЯ


В оригинале файл со списком маршрутов имеет аргумент TYPE в конце аргумента FROM, например /usr/russ/bin/*. Это представляет проблему (кроме того, что показывает, что ваш автор еще не является мастером!), потому что когда символ * будет выделен, он будет расширен в имена всех файлов вместо того, чтобы трактоваться как литеральный символ. Простое решение - использовать отдельные поля, что и было сделано. Мастерским решением является экранировать метасимвол для сохранения его как литерального символа. Как только символ * будет выделен из маршрутного имени, символ \ представит его в виде * вместо того, чтобы дать его на расширение. Например, можно написать так:

TYPE=`basename \"$FROM"` Здесь символ * присваивается переменной TYPE, вместо того, чтобы присвоить TYPE список всех файлов, которые соответствуют метасимволу. Затем, когда будет вызвана команда find, переменная TYPE должна быть экранирована так, чтобы метасимвол интерпретировался не командным процессором, а самой командой find.



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


В нынешней реализации никакие дополнительные ключи не допускается передача никаких дополнительных ключей команде cpio. Что случится, если вы захотели заменить копирование файлов, где это возможно, созданием ссылок (ключ -l) или не менять время последнего доступа к исходному файлу при его копировании (опция -a)? Такие возможности были бы недопустимы.

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

Для этого потребуется такой цикл:

for ARG in $* do if [ "`echo $ARG|cut -c1`" = "-" ] then CPIOARG="CPIOARG $ARG" shift fi done

Затем переменная CPIOARG может быть передана команде cpio.

Еще одна область, где могут быть произведены изменения - это управление правами доступа к файлам. Как объяснялось ранее, значение 0для umask делает все права такими, что они разрешают запись. Если это вам не подходит, оператор find может быть изменен так, что будет производиться выборочное копирование (и изменение прав доступа).

Предположим, например, вы имеете каталог с двумя файлами. Если выполнился оператор "find /dir -print", список файлов будет таким:

/dir /dir/file1 /dir/file2

Обратите внимание, что имя каталога появляется первым. Проблема возникает, если имя каталога не принадлежит вам или вы не имеете права записи. Происходит следующее: имя каталога копируется первым, устанавливаются права доступа (блокируя вас) и после этого file1 и file2 не могут быть скопированы в каталог dir. В cptdir мы применяем решение изменить umask так, чтобы вы всегда имели права записи. Это своего рода клудж, но он работает.

Другой путь - это изменить оператор find. Выполнение оператора "find /dir -depth -print" сгенерирует такой список файлов:

/dir/file1 /dir/file2 /dir

Обратите внимание, что имя каталога стоит ПОСЛЕДНИМ! Это правильно. Ключ -depth переворачивает список файлов так, что имя каталога печатается последним.

Что это дает? Фокус в том, что сначала копируются file1 и file2, а затем устанавливаются права доступа данного каталога. Вы можете записать файлы в каталог, для которого вы не имеете права записи. Благодаря тому, что файлы копируются первыми, вы можете не беспокоиться о том, какого рода права доступа имеет этот каталог. К сожалению, ключ-depth команды find поддерживается не всеми версиями системы UNIX.


Одно из мест, где вы можете настраивать dosflp, это регулярные выражения. Уже включены выражения для .asm, .c и .h, но вы можете изменить это или добавить больше ключей для любой последовательности, которую вы часто используете.




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



с малым числом пользователей порождает


Даже "небольшая" система UNIX с малым числом пользователей порождает сотни файлов в ходе обычной работы. В процессе программирования вы можете создавать множество файлов для различных версий ваших программ. Ведение почты и запись текста при помощи редактора vi способствует тому, что накапливается еще больше файлов. Такие утилиты, как uucp, lp и другие добавляют еще больше файлов. Если у вас система UNIX установлена на микро-ЭВМ, то ваш жесткий диск начинает переполняться. В больших многопользовательских системах дисковая память редко считается проблемой, но в действительности всегда кажется, будто файлы стремятся расшириться до заполнения всей доступной дисковой памяти. Поэтому каждый пользователь должен нести ответственность за расход дискового пространства. (Если вы платите за дисковую память, то у вас также могут быть финансовые стимулы.) Однако, то, что вы хотите сохранить, вы хотите СОХРАНИТЬ. Именно здесь начинается работа по созданию резервных копий.

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


Программирование и управление документацией


В данной главе представлен набор командных файлов командного процессора для извлечения документирующей информации из исходного кода программ на Си и командных файлов командного процессора. Используются две стратегии. Первая состоит в том, что, следуя стандартной "модели документации" в исходном коде, вы можете придумать командные файлы, которые просто "вытягивают" самые новые разделы с заголовочной информацией из файлов с исходным кодом и собирают их затем в новый файл. Такие файлы служат в качестве каркаса для документации по программе. Следовательно, при условии, что заголовки исходного кода изменяются разными программистами стандартным образом, простая команда UNIX может извлечь полностью новый каркас руководства.

Этот подход реализуют командные файлы stripc, stripf и strips. Stripc и stripf предоставляют листинги блоков документации уровня файла и уровня функций из ваших исходных файлов на Си, а strips извлекает документацию из командных файлов командного процессора.

Второй подход - доступ к определенным видам структур (таким как функции на Си) в теле самого программного кода. Этим методом вы можете точно найти, как называется данная функция, без сосредоточенного изучения горы листингов. Командный файл ctags является и полезным инструментом, и моделью применения этого подхода к другим видам программных структур.

Ctags объединяет свой выводной файл с редактором vi/ex с целью предоставления простого способа доступа к любой заданной функции и ее просмотра, копирования или редактирования в текущей программе. Ctags делает это путем предоставления признаков, которые понимает vi, для каждой функции, обнаруженной в любом указанном наборе файлов. Таким образом, вы можете использовать простую команду редактора, чтобы получить то, что вам нужно. Вы больше не обязаны заботиться о том, какой файл содержит какую функцию. Ctags - отличный пример применения мощи UNIX в полном объеме.

Имея такие инструментальные средства, вам не нужно изобретать колесо, так как вы можете легко находить и выбирать те средства, которые необходимы вам в конкретном приложении. Вы уже написали программу управления терминалом Trantor TR-101? Примените ctags и найдите ее. Более того, самодокументируемый напечатанный файл и документация о функциях, полученная с помощью этих командных файлов, дают другим программистам хороший старт в понимании того, что вы сделали. Это даже может слегка произвести впечатление на вашего начальника.

Каким в общих чертах будет наш подход к созданию таких командных файлов? У нас есть некоторые потенциальные преимущества в применении такого вида доступа в системе UNIX. Прежде всего, исходные файлы не отличаются от других текстовых файлов, поэтому мы можем использовать все имеющиеся в UNIX средства поиска и распознавания шаблонов (sed, awk и т.д.), чтобы находить символьные строки. Во-вторых, мы освоили технику обхода файловых деревьев и работы с отобранными типами файлов, описанную в предыдущих главах. Наш подход состоит в объединении этих средств таким образом, чтобы они обеспечивали доступ к структурированной документации, содержащейся в программных файлах.



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 производится эхо-отображение прогона формата на экран и берется следующий файл. Так продолжается до тех пор, пока все файлы в командной строке не будут обработаны.


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


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


Вы решили рискнуть. Продукт на


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