Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 22 (всего у книги 55 страниц)
nftw()
После запуска nftw()
она вызывает функцию, указатель для которой предоставляете вы. (Такие функции называются функциями обратного вызова (callback functions), поскольку они «вызываются обратно» из библиотечного кода.) Функция обратного вызова получает четыре аргумента:
const char *file
Имя текущего обрабатываемого файла (каталога, символической ссылки и т.д.).
const struct stat *sb
Указатель на struct stat
для файла.
int flag
Одно из нескольких значений флагов (описанных ниже), указывающих, какой это вид файла или была ли ошибка для объекта.
struct FTW *s
Эта структура предоставляет две отдельные части информации:
struct FTW {
int base; /* Индекс в файле базовой части имени файла */
int level; /* Глубина этого элемента относительно точки отсчета */
};
Параметр flag
имеет одно из перечисленных в табл. 8.4 значений.
Таблица 8.4. Значения флагов для функции обратного вызова nftw()
FTW_F | Объект является обычным файлом |
FTW_D | Объект является каталогом |
FTW_DNR | Объект является каталогом, который нельзя прочесть |
FTW_SL | Объект является символической ссылкой |
FTW_NS | Объект не является символической ссылкой, а stat() потерпела неудачу |
FTW_DP | Объект является каталогом, элементы которого были уже обработаны. Это может случиться, лишь когда в вызове nftw() использовался FTW_DEPTH |
FTW_SLN | Объект является символической ссылкой, указывающей на несуществующий файл. Это может случиться, лишь когда в вызове nftw() не используется FTW_PHYS |
struct FTW* s
предоставляет дополнительную информацию, которая может быть полезной. s->bas
e действует в качестве индекса в file
; file
является полным путем обрабатываемого объекта (относительно точки отсчета), 'file + s->base
' указывает на первый символ компонента имени файла.
s->level
указывает текущую глубину иерархии; считается, что первоначальная точка отсчета находится на уровне 0.
Функция обратного вызова должна вернуть 0, если все нормально. Любое ненулевое возвращенное значение заставляет nftw()
прекратить свою обработку и вернуть то самое ненулевое значение. Справочная страница отмечает, что функция обратного вызова должна останавливать обработку только путем возвращаемого значения, чтобы у nftw()
был шанс произвести очистку: т.е. освободить динамически выделенную память, закрыть открытые дескрипторы файлов и т.д. Функции обратного вызова не следует использовать longjmp()
, если только программа не завершается немедленно, (longjmp()
является продвинутой функцией, которую мы опишем в разделе 12.5 «Нелокальные goto».) Рекомендуемой методикой обработки ошибок является установка глобальной переменной, указывающей на наличие проблем, возвращение 0 из функции обратного вызова и обработка ошибок после завершения перемещения nftw()
по иерархии файлов. (GNU du
это делает, как мы вскоре увидим.)
Давайте свяжем все это воедино в примере программы. ch08-nftw.c
обрабатывает каждый файл или каталог, указанный в командной строке, запуская для них nftw()
. Функция, обрабатывающая каждый файл, выводите отступом имя и тип файла, показывая иерархическое положение каждого файла. Для разнообразия мы сначала покажем результаты, а затем покажем и обсудим программу:
$ pwd /* Где мы находимся */
/ home/аrnold/work/prenhall/progex
$ code/ch08/ch08-nftw code /* Обойти каталог 'code' */
code (directory) /* Каталог верхнего уровня */
ch02 (directory) /* Подкаталоги с отступом на один уровень */
ch02-printenv.c (file) /* Файлы в подкаталоге с отступом
на два уровня */
ch03 (directory)
ch03-memaddr.c (file)
ch04 (directory)
ch04-holes.c (file)
ch04-cat.с (file)
ch04-maxfds.c (file)
v7cat.c (file)
...
Вот сама программа:
1 /* ch08-nftw.c – демонстрирует nftw() */
2
3 #define _XOPEN_SOURCE 1 /* Требуется под GLIBC для nftw() */
4 #define _XOPEN_SOURCE_EXTENDED 1 /* To же */
5
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12
13 #define SPARE_FDS 5 /* fds для использования другими функциями, см. текст */
14
15 extern int process(const char *file, const struct stat *sb,
16 int flag, struct FTW *s);
17
18 /* usage – print message and die */
19
20 void usage(const char *name)
21 {
22 fprintf(stderr, "usage: %s (-c) directory ...n", name);
23 exit(1);
24 }
25
26 /* main – вызвать nftw() для каждого аргумента командной строки */
27
28 int main(int argc, char **argv)
29 {
30 int i, c, nfds;
31 int errors = 0;
32 int flags = FTW_PHYS;
33 char start[PATH_MAX], finish[PATH_MAX];
34
35 while ((c = getopt(argc, argv, "с")) != -1) {
36 switch (c) {
37 case 'c':
38 flags |= FTW_CHDIR;
39 break;
40 default:
41 usage(argv[0]);
42 break;
43 }
44 }
45
46 if (optind == argc)
47 usage(argv[0]);
48
49 getcwd(start, sizeof start);
50
51 nfds = getdtablesize() – SPARE_FDS; /* оставить несколько запасных дескрипторов */
52 for (i = optind; i < argc; i++) {
53 if (nftw(argv[i], process, nfds, flags) != 0) {
54 fprintf(stderr, "%s: %s: stopped earlyn",
55 argv[0], argv[i]);
56 errors++;
57 }
58 }
59
60 if ((flags & FTW_CHDIR) != 0) {
61 getcwd(finish, sizeof finish);
62 printf("Starting dir: %sn", start);
63 printf("Finishing dir: %sn", finish);
64 }
65
66 return (errors != 0);
67 }
Строки 3–11 включают заголовочные файлы. По крайней мере в GLIBC 2.3.2 перед включением любого заголовочного файла необходимы #define
для _XOPEN_SOURCE
и _XOPEN_SOURCE_EXTENDED
. Они дают возможность получить объявления и значения флагов, которые nftw()
предоставляет свыше предоставляемых ftw()
. Это специфично для GLIBC. Потребность в этом в конечном счете исчезнет, когда GLIBC станет полностью совместимой со стандартом POSIX 2001.
Строки 35–44 обрабатывают опции. Опция -с
добавляет к флагам nftw() FTW_CHDIR
. Это эксперимент с целью увидеть, сможете ли вы оказаться где-то в другом месте от того, где начинали. Кажется, это возможно, если nftw()
завершается неудачей, в противном случае вы заканчиваете там же, где начинали. (POSIX не документирует это явным образом, но целью, похоже, было действительно заканчивать там же, где начинали. Стандарт не говорит, что функция обратного вызова не должна менять текущий каталог.)
Строка 49 сохраняет начальный каталог для дальнейшего использования, пользуясь getcwd()
.
Строка 51 вычисляет число дескрипторов, которые может использовать nftw()
. Мы не хотим, чтобы она использовала все доступные дескрипторы файлов, если функция обратного вызова также хочет открывать файлы. В вычислении используется getdtablesize()
(см. раздел 4.4.1 «Понятие о дескрипторах файлов») для получения максимально возможного числа и вычета из него SPARE_FDS
, который был вычислен ранее в строке 13.
Эта процедура служит основанием для больших объяснений. В обычном случае по крайней мере три дескриптора уже используются для стандартного ввода, стандартного вывода и стандартной ошибки. nftw()
нужно некоторое количество дескрипторов файлов для открытия и чтения каталогов; внутри себя opendir()
использует open()
при открытии каталогов для чтения. Если функции обратного вызова также нужно открывать файлы, мы должны предотвратить израсходование функцией nftw()
всех доступных дескрипторов файлов для открывания каталогов. Мы делаем это, вычитая некоторое число из максимально допустимого. Для данного примера мы выбрали пять, но если функции обратного вызова нужно открывать файлы, должно использоваться большее число, (nftw()
знает, как восстановиться при израсходовании дескрипторов файлов; мы не должны беспокоиться о таком случае.)
Строки 52–58 являются главным циклом над нашими аргументами; строки 53–57 проверяют ошибки; когда они появляются, код выводит диагностическое сообщение и увеличивает значение переменной errors
.
Строки 60–64 являются частью эксперимента с FTW_CHDIR
, выводящего начальный и конечный каталоги, если было использовано -с
.
По-настоящему интересной функцией является process()
; это функция обратного вызова, которая обрабатывает каждый файл. Она использует базовый шаблон для функции обратного вызова nftw()
, который является оператором switch
для значения flag
.
69 /* process – выводит каждый файл на нужном уровне */
70
71 int process(const char "file, const struct stat *sb,
72 int flag, struct FTW *s)
73 {
74 int retval = 0;
75 const char *name = file + s->base;
76
77 printf("%*s", s->level * 4, ""); /* сделать отступ */
78
79 switch (flag) {
80 case FTW_F:
81 printf("%s (file)n", name);
82 break;
83 case FTW_D:
84 printf("%s (directory)n", name);
85 break;
86 case FTW_DNR:
87 printf("%s (unreadable directory)n", name);
88 break;
89 case FTW_SL:
90 printf("%s (symbolic link)n", name);
91 break;
92 case FTW_NS:
93 printf("%s (stat failed): %sn", name, strerror(errno));
94 break;
95 case FTW_DP:
96 case FTW_SLN:
97 printf("%s: FTW_DP or FTW_SLN: can't happen'n", name);
98 retval = 1;
99 break;
100 default:
101 printf("%s: unknown flag %d: can't happen'n", name, flag);
102 retval = 1;
103 break;
104 }
105
106 return retval;
107 }
Строка 75 использует 'file + s->base
' для получения имени из полного пути. Это значение указателя сохраняется в переменной name
для повторного использования в функции.
Строка 77 делает отступ нужного размера, используя красивый трюк. Используя %*s
, printf()
получает от первого аргумента ширину поля. Это вычисляется динамически как 'level * 4
'. Строка, которая должна быть выведена – «», пустая строка. Конечным результатом является то, что printf()
создает для нас отступ нужного размера без необходимости запуска цикла.
Строки 79–104 являются оператором switch
. В данном случае он не делает ничего весьма интересного, кроме вывода имени файла и его типа (файл, каталог и т.д.)
Хотя эта программа не использует struct stat
, должно быть ясно, что вы могли бы сделать в функции обратного вызова все, что хотите.
8.5. Обход дерева файлов: GNUЗАМЕЧАНИЕ. Джим Мейеринг (Jim Meyering), сопроводитель GNU Coreutils, замечает, что дизайн
nftw()
несовершенен из-за ее рекурсивной природы. (Она рекурсивно вызывает себя при обработке подкаталогов.) Если иерархия каталогов становится действительно глубокой, в диапазоне уровней 20 000–40 000 (!),nftw()
может выйти за пределы размера стека, уничтожив программу. Есть также и другие проблемы, связанные с дизайномnftw()
. Версия GNU Coreutils после 5.0 исправляет это путем использования набора процедурfts()
(см. fts(3)).
du
GNU версия du
в GNU Coreutils использует nftw()
для обхода одной или более иерархии файлов, собирая и выводя сведения, касающиеся количества используемого дискового пространства. У нее большое число опций, которые управляют ее поведением но отношению к символическим ссылкам, форматом вывода чисел и т.д. Это делает разбор кода труднее, чем могло бы быть при более простой версии. (Однако, мы не собираемся позволить этому остановить нас.) Вот сводка опций du
, которые вскоре будут полезны, когда мы рассмотрим код.
$ du –help
Usage: du [OPTION]... [FILE]...
Дает сводку использования диска для каждого FILE,
рекурсивно для каталогов.
Обязательные для длинных опций аргументы являются обязательными
также и для коротких опций.
-a, –all записать число всех файлов, а не только
каталогов
–apparent-size вывести видимые размеры, а не использование
диска; хотя видимый размер обычно меньше, он
может быть и больше из-за дыр в файлах,
внутренней фрагментации, косвенных блоков и т.п.
-В, –block-size=SIZE использовать блоки размером SIZE байтов
-b, –bytes эквивалентно '–apparent-size –block-size=1'
-с, –total выводит итоговую сумму
-D, –dereference-args разыменовывать FILE, которые являются
символическими ссылками
-h, –human-readable вывести размеры в удобном для восприятия
формате (например, 1K 234М 2G)
-Н, –si так же, но использовать степени 1000, не 1024
-k подобно –block-size=1K
-l, –count-links считать размеры несколько раз при прямых
ссылках
-L, –dereference разыменовывать все символические ссылки
-S, –separate-dirs не включать размер подкаталогов
-s, –summarize отобразить для каждого аргумента лишь итоги
-х, –one-file-system пропускать каталоги на различных файловых
системах
-X –exclude– исключить файлы, подходящие под любой
FILE from=FILE образец в FILE
–exclude=PATTERN исключить файлы, соответствующие PATTERN
–max-depth=N вывести итог для каталога (или файла, с –all)
только если он находится на N или менее уровней
глубже аргумента командной строки;
–max-depth=0 то же самое, что и –summarize
–help отобразить экран справки и выйти
–version вывести сведения о версии и выйти
SIZE может быть (или может быть целым, за которым
может следовать это) одним из
следующих: kB 1000, K 1024, MB 1 000 000, M 1 048 576 и т.д.
для G, T, Р, E, Z, Y.
Чтобы еще больше усложнить дело, du
использует частную версию nftw()
, у которой есть несколько расширений. Сначала имеются дополнительные значения флагов для функции обратного вызова:
FTW_DCHP
Это значение означает, что nftw()
не может выполнять 'chdir("..")
'.
FTW_DCH
Это значение означает, что nftw()
не может использовать chdir()
для перехода в сам каталог.
FTW_DPRE
Частная nftw()
вызывает для каталогов функцию обратного вызова дважды. Это значение используется при первой встрече с каталогом. После обработки всех нижележащих объектов каталога используется стандартное значение FTW_DP
.
Частная nftw()
добавляет также в struct FTW
новый член, int skip
. Если текущий объект является каталогом и функция обратного вызова устанавливает в поле skip
ненулевое значение, nftw()
не будет больше обрабатывать этот каталог. (Функция обратного вызова должна установить skip
таким образом, когда flag
равен FTW_DPRE
; делать это для FTW_DP
слишком поздно.)
С этим объяснением за поясом, вот функция process_file()
из du.c
. Номера строк приведены относительно начала функции:
1 /* Эта функция вызывается один раз для каждого объекта файловой
2 системы, с которой сталкивается nftw. nftw осуществляет сначала
3 поиск вглубь. Эта функция знает это и собирает итоги для каталогов
4 на основе изменений в глубине текущего элемента. */
5
6 static int
7 process_file(const char *file, const struct stat *sb,
8 int file_type, struct FTW *info)
9 {
10 uintmax_t size;
11 uintmax_t size_to_print;
12 static int first_call = 1;
13 static size_t prev_level;
14 static size_t n_alloc;
15 static uintmax_t *sum_ent;
16 static uintmax_t *sum_subdir;
17 int print = 1;
18
19 /* Всегда определяйте info->skip перед возвратом. */
20 info->skip = excluded_filename(exclude, file + info->base);
/* Для –exclude */
Эта функция делает многое, поскольку ей приходится реализовать все опции du
. Строка 17 устанавливает print
в true (1); по умолчанию выводятся сведения о каждом файле. Дальнейший код устанавливает ее при необходимости в false (0).
Строка 20 устанавливает info->skip
на основе опции –exclude
. Обратите внимание, что это исключает подкаталоги, если каталог совпадает с шаблоном для –exclude
.
22 switch (file_type)
23 {
24 case FTW_NS:
25 error (0, errno, _("cannot access %s"), quote(file));
26 G_fail = 1; /* Установить глобальную переменную для дальнейшего */
27 return 0; /* Вернуть 0 для продолжения */
28
29 case FTW_DCHP:
30 error(0, errno, _("cannot change to parent of directory %s"),
31 quote(file));
32 G_fail = 1;
33 return 0;
34
35 case FTW_DCH:
36 /* Нельзя просто вернуться, поскольку, хотя nftw не может войти в
37 каталог, она может использовать stat, постольку у нас есть размер */
38 error(0, errno, _("cannot change to directory %s"), quote(file));
39 G_fail = 1;
40 break;
41
42 case FTW_DNR:
43 /* Нельзя просто вернуться, поскольку, хотя nftw не может прочесть
44 каталог, она может вызвать stat, постольку у нас есть размер. */
45 error(0, errno, _("cannot read directory %s"), quote(file));
46 G_fail = 1;
47 break;
48
49 default:
50 break;
51 }
52
53 /* Если это первая (предварительная) встреча с каталогом,
54 сразу вернуться. */
55 if (file_type == FTW_DPRE)
56 return 0;
Строки 22–51 являются стандартным оператором switch
. Ошибки, для которых нет информации о размере, устанавливают глобальную переменную G_fail
в 1 и возвращают 0, чтобы продолжить обработку (см строки 24–27 и 29–33). Ошибки, для которых есть размер, также устанавливают G_fail
, но затем прерывают switch
для того, чтобы обработать статистику (см. строки 35–40 и 42–47).
Строки 55–56 сразу завершают функцию, если это первая встреча с каталогом
58 /* Если файл исключается или если он уже учитывался
59 через прямую ссылку, не включать его в сумму. */
60 if (info->skip,
61 || (!opt_count_all
62 && 1 < sb->st_nlink
63 && hash_ins(sb->st_ino, sb->st_dev)))
64 {
65 /* Заметьте, мы не должны здесь просто возвращаться.
66 Нам все еще нужно обновить prev_level и, возможно, передать
67 некоторые суммы выше по иерархии. */
68 size = 0;
69 print = 0;
70 }
71 else
72 {
73 size = (apparent_size
74 ? sb->st_size
75 : ST_NBLOCKS (*sb) * ST_NBLOCKSIZE);
76 }
Теперь становится интересно. По умолчанию du
подсчитывает пространство, занимаемое прямыми ссылками, лишь одни раз. Опция –count-links
заставляет ее подсчитывать пространство для каждой ссылки; переменная opt_count_all
равна true, когда указана –count-links
. Для отслеживания ссылок du
содержит хэш-таблицу[87]87
Хэш-таблица является структурой данных, позволяющей быстрое получение сохраненной информации, подробности выходят за рамки данной книги – Примеч. автора.
[Закрыть] уже встречавшихся пар (устройство, индекс).
Строки 60–63 проверяют, следует ли не включать файл в подсчет, либо из-за того, что он был исключен (info->skip
равно true, строка 60), либо потому что не была указана –count-links
(строка 61) и у файла несколько ссылок (строка 62) и файл уже находится в хеш-таблице (строка 63). В этом случае размер устанавливается в 0, поэтому он не входит в конечную сумму, a print
также устанавливается в false (строки 68–69).
Если ни одно из этих условий не отмечается, размер вычисляется либо в соответствии с размером в struct stat
, либо в соответствии с числом блоков диска (строки 73–75) Это решение основывается на переменной apparent_size
, которая установлена при использовании опции –apparent-size
.
78 if (first_call)
79 {
80 n_alloc = info->level + 10; /* Allocate arrays */
81 sum_ent = XCALLOC(uintmax_t, n_alloc); /* to hold sums */
82 sum_subdir = XCALLOC(uintmax_t, n_alloc);
83 }
84 else
85 {
86 /* FIXME: Стыдно, что нам нужно приводить к типу size_t для избежания
87 предупреждений gcc о 'сравнении между знаковым и беззнаковым'.
88 Возможно, неизбежно, при условии, что члены структуры FTW
89 имеют тип 'int' (исторически), так как мне нужно, чтобы переменные
90 вроде n_alloc и prev_level имели осмысленные типы. */
91 if (n_alloc <= (size_t)info->level)
92 {
93 n_alloc = info->level * 2; /* Удвоить сумму */
94 sum_ent = XREALLOC(sum_ent, uintmax_t, realloc); /* И выделить повторно */
95 sum_subdir = XREALLOC(sum_subdir, uintmax_t, n_alloc);
96 }
97 }
98
99 size_to_print = size;
Строки 78–97 управляют динамической памятью, используемой для хранения статистики о размере файла, first_call
является статической переменной (строка 12), которая равна true при первом вызове process_file()
. В этом случае вызывается calloc()
(через упаковывающий макрос в строках 81–82; это обсуждалось в разделе 3.2.1.8 «Пример чтение строк произвольной длины»). Остальную часть времени first_call
равно false, и используется realloc()
(снова через упаковывающий макрос, строки 91–96).
Строка 99 заносит значение size
в size_to_print
; эта переменная может обновляться в зависимости от того, должна ли она включать размеры дочерних элементов. Хотя size
могла бы использоваться повторно, отдельная переменная упрощает чтение кода.
101 if (!first_call)
102 {
103 if ((size_t)info->level == prev_level)
104 {
105 /* Обычно самый частый случай. Ничего не делать. */
106 }
107 else if ((size_t)info->level > prev_level)
108 {
109 /* Нисхождение по иерархии.
110 Очистить аккумуляторы для *всех* уровней между prev_level
111 и текущим. Глубина может значительно меняться,
112 например, от 1 до 10. */
113 int i;
114 for (i = prev_level +1; i <= info->level; i++)
115 sum_ent[i] = sum_subdir[i] = 0;
116 }
117 else /* info->level < prev_level */
118 {
119 /* Восхождение по иерархии.
120 nftw обрабатывает каталог лишь после всех элементов,
121 в которых был обработан каталог. Когда глубина уменьшается,
122 передать суммы от детей (prev_level) родителям.
123 Здесь текущий уровень всегда меньше, чем
124 предыдущий. */
125 assert (
126 size_to_print += sum_ent[prev_level];
127 if (!opt_separate_dirs)
128 size_to_print += sum_subdir[prev_level];
129 sum_subdir[info->level] += (sum_ent[prev_level]
130 + sum_subdir[prev_level]);
131 }
132 }
Строки 101–132 сравнивают текущий уровень с предыдущим. Возможны три случая.
Уровни те же самые
В этом случае нет необходимости беспокоиться о статистике дочерних элементов. (Строки 103–106.)
Текущий уровень выше предыдущего
В этом случае мы спустились по иерархии, и статистику нужно восстановить (строки 107–116). Термин «аккумулятор» в комментарии подходящий: каждый элемент аккумулирует общее дисковое пространство, использованное на этом уровне. (На заре вычислительной техники регистры центрального процессора часто назывались «аккумуляторами».)
Текущий уровень ниже предыдущего
В этом случае мы завершили обработку всех дочерних элементов каталога и только что вернулись обратно в родительский каталог (строки 117–131). Код обновляет суммы, включая size_to_print
.
134 prev_level = info->level; /* Установить статические переменные */
135 first_call = 0;
136
137 /* Включить элемент каталога в общую сумму для содержащего
138 каталога, если не указана –separate-dirs (-S). */
139 if (!(opt_separate_dirs && IS_FTW_DIR_TYPE(file_type)))
140 sum_ent[info->level] += size;
141
142 /* Даже если каталог нельзя прочесть или перейти в него,
143 включить его размер в общую сумму, ... */
144 tot_size += size;
145
146 /* ...но не выводить для него итог, поскольку без размера(-ов)
147 потенциальных элементов, это может сильно запутывать. */
148 if (file_type == FTW_DNR || file_type == FTW_DCH)
149 return 0;
150
151 /* Если мы не считаем элемент, например, потому что это прямая
152 ссылка на файл, который уже посчитан (и –count-links), не
153 выводить для него строку. */
154 if (!print)
155 return 0;
Строки 134–135 устанавливают статические переменные prev_level
и first_call
таким образом, что они содержат правильные значения для последующего вызова process_file()
, гарантируя, что весь предыдущий код работает правильно.
Строки 137–144 выверяют статистику на основе опций и типа файла. Комментарии и код достаточно просты. Строки 146–155 сразу завершают функцию, если сведения не должны выводиться.
157 /* FIXME: Это выглядит подозрительно годным для упрощения. */
158 if ((IS_FTW_DIR_TYPE(file_type) &&
159 (info->level <= max_depth || info->level == 0))
160 || <(opt_all && info->level <= max_depth) || info->level == 0))
161 {
162 print_only_size(size_to_print);
163 fputc('t', stdout);
164 if (arg_length)
165 {
166 /* Вывести имя файла, но без суффикса каталога '.' или '/.'
167 который мы, возможно, добавили в main. */
168 /* Вывести все до добавленной нами части. */
169 fwrite(file, arg_length, 1, stdout);
170 /* Вывести все после добавленного нами. */
171 fputs(file + arg_length + suffix_length
172 + (file[arg_length + suffix_length] == '/'), stdout);
173 }
174 else
175 {
176 fputs(file, stdout);
177 }
178 fputc('n', stdout);
179 fflush(stdout);
180 }
181
182 return 0;
183 }
Условие в строках 158–160 сбивает с толку, и комментарий в строке 157 указывает на это. Условие утверждает: «Если (1a) файл является каталогом и (1b) уровень меньше максимального для вывода (переменные – -max-depth
и max_depth
) или нулевой, или (2a) должны быть выведены все файлы и уровень меньше, чем максимальный для вывода, или (2b) уровень нулевой», тогда вывести файл. (Версия du
после 5.0 использует в этом случае несколько менее запутанное условие.)
Строки 162–179 осуществляют вывод. Строки 162–163 выводят размер и символ TAB Строки 164–173 обрабатывают специальный случай. Это объяснено далее в du.c
, в строках файла 524–529:
524 /* При разыменовании лишь аргументов командной строки мы
525 используем флаг nftw FTW_PHYS, поэтому символическая ссылка
526 на каталог, указанная в командной строке, в норме не
527 разыменовывается. Для решения этого мы идем на издержки,
528 сначала добавляя '/.' (или '.'), а затем удаляем их каждый раз
529 при выводе имени производного файла или каталога. */
В этом случае arg_length
равен true, поэтому строки 164–173 должны вывести первоначальное имя, а не измененное В противном случае строки 174–177 могут вывести имя как есть.
Фу! Куча кода. Мы находим, что это верхний уровень спектра сложности, по крайней мере, насколько это может быть просто представлено в книге данного содержания. Однако, он демонстрирует, что код из реальной жизни часто бывает сложным. Лучшим способом справиться с этой сложностью является ясное именование переменных и подробные комментарии du.с
в этом отношении хорош; мы довольно легко смогли извлечь и изучить код без необходимости показывать все 735 строк программы!