Программирование в стандарте POSIX

Средства обработки структурированных данных

Разбить на страницы
Показывать лекцию целиком

Простейшие средства обработки текстовых файлов

Согласно определению, данному в стандарте POSIX-2001, текстовым называется файл, символы которого объединены в строки длиной не более {LINE_MAX}, не содержащие нулевого символа.

Вероятно, простейшей операцией с файлами (не обязательно текстовыми) является их выдача на стандартный вывод, что обеспечивается посредством утилиты

cat [-u] [файл ...]

Несмотря на внешнюю простоту, применение служебной программы cat сопряжено с рядом тонкостей. Во-первых, опция -u предписывает передавать на стандартный вывод байты из входных файлов без задержек, это означает, в частности, отмену буферизации стандартного вывода. Во-вторых, если не указаны исходные файлы или в качестве аргумента задан знак минус, используется стандартный ввод. Минус может фигурировать в командной строке несколько раз, стандартный ввод не закрывается и не переоткрывается. Например, допустимы команды, показанные в листинге 6.1, причем первая обеспечит ввод двух интерактивных вставок, а вторая эквивалентна третьей (читателю предлагается самостоятельно объяснить данный факт).

cat f1.txt - f2.txt - f3.txt
cat g1.txt - g2.txt - g3.txt < input.txt
cat g1.txt - g2.txt /dev/null g3.txt < input.txt
cat f1.txt f2.txt > f1.txt

В-третьих, нужно учитывать особенности перенаправления вывода командным интерпретатором shell. В общем случае утилита cat выдает результат конкатенации содержимого файлов -аргументов, но выполнение четвертой команды, показанной в листинге 6.1, приведет к утрате содержимого файла f1.txt.

Для просмотра больших текстовых файлов предпочтительнее пользоваться служебной программой

more [опция ...] [файл ...]

Она разбивает вывод на страницы (экраны) и выдает их по явному указанию пользователя (например, по нажатию клавиши пробела).

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

-n число

Задает число строк, выдаваемых на экран.

-p команды more

Команды more выполняются после входа в очередной файл и вывода его первого экрана. Это может быть, к примеру, команда поиска.

-s

Сжимать последовательные пустые строки в одну.

Утилита more часто используется как заключительное звено конвейеров для удобного просмотра результатов.

Для просмотра нетекстовых файлов рекомендуется служебная программа

od [опция ...] [файл ...]

Она выдает на стандартный вывод содержимое исходных файлов в формате, заданном пользователем, а также в виде блоков, каждый из которых в простейшем (и наиболее употребительном) случае состоит из шестнадцати байт и занимает при выводе одну строку. В начале подобной строки располагается смещение блока от начала файла, затем следует содержимое блока. С помощью аргумента опции -A можно задать основание системы счисления для смещений ( d - десятичное, o - восьмеричное, x - шестнадцатеричное, n - не выдавать смещение). Формат выдачи содержимого определяется аргументом опции -t. Помимо перечисленных типов могут быть заданы a ( именованные символы ), c (символы), f (вещественные числа), u (беззнаковые десятичные). За спецификациями d, f, o, u и x может следовать десятичное число - количество байт в одном значении заданного типа; за спецификацией f - символы F, D или L, указывающие тип вещественных чисел ( float, double или long double, соответственно), а за спецификациями d, o, u, x - спецификаторы целочисленного типа C ( char ), S ( short ), I ( int ) или L ( long ).

Рассмотрим пример. Пусть файл ascii.tab содержит байты от

od -A x -t a ascii.tab
000000 nul soh stx etx eot enq ack bel  bs  ht  nl  vt  ff  cr  so  si
000010 dle dc1 dc2 dc3 dc4 nak syn etb can  em sub esc  fs  gs  rs  us
000020  sp   !   "   #   $   %      '   (   )   *   +   ,   -   .   /
000030   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
000040   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
000050   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
000060   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
000070   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~ del
000080

Полезная возможность - взглянуть на начало файла, чтобы понять, нуждается ли он в более детальном изучении. Эта возможность реализуется служебной программой head:

head [-n число] [файл ...]

Утилита head копирует на стандартный вывод указанное число (по умолчанию - 10) начальных строк исходных файлов (или весь файл, если он слишком короткий).

"Симметричная" служебная программа - tail

tail [-f] [-c число | -n число] [файл]

выдает на стандартный вывод конец исходного файла, начиная с указанного места. Если задана опция -c, единицей измерения является байт, если -n - строка. Числовой аргумент опции может быть задан со знаком; знак + означает, что отсчет байт (строк) идет от начала файла.

Опция -f полезна для слежения за растущими файлами, в которые пишет какой-либо другой процесс. При наличии этой опции утилита tail не завершается по достижении конца файла, но ждет появления новых данных и выводит их.

Рассмотрим примеры. Команда

tail -f f1

выдаст последние десять строк файла f1, за которыми последуют строки, добавленные к файлу f1 в промежуток времени между запуском и терминированием утилиты tail.

В качестве примера совместного использования служебных программ head и tail приведем фрагмент одного из командных файлов ОС Linux (см. листинг 6.4).

echo "Reloading automounter: checking\
for changes ... "
TMP=/var/run/autofs.tmp
getmounts > $TMP
for i in /var/run/autofs.*.pid
do
 pid=`head -n 1 $i 2>/dev/null`
 [ "$pid" = "" ]  continue
 command=`tail -n +2 $i`
 if ! grep -q "^$command" $TMP
 then
   echo "Stopping automounter: $command"
   kill -USR2 $pid
 fi
done
rm -f $TMP

Отметим, что здесь утилиты head и tail делят исходный файл "по-братски": первой достается первая строка, второй - все остальные.

Служебная программа pr

pr [+номер_страницы] [-число_столбцов] 
[-adFmrt] [-e[символ][число]]
[-h заголовок] [-i[символ][число]] 
[-l число_строк] [-n[символ][ширина]]
[-o отступ] [-s[символ]] [-w ширина] 
[файл ...]

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

Утилита pr обрабатывает следующие аргументы командной строки.

+номер_страницы

Начать вывод со страницы с заданным номером (по умолчанию с первой).

-число_столбцов

Вывод в заданное число столбцов (по умолчанию в один). При выводе в несколько столбцов автоматически действуют опции -e и -i. Этот аргумент несовместим с опцией -m.

-a

Выдача в несколько столбцов с упорядочением по строкам. Например, если заказана печать в два столбца, первая исходная строка попадает в первый столбец, вторая - во второй, третья - снова в первый и т.п.

-d

Выдача через строку.

-e[символ][число]

Установка табуляции через заданное число позиций, начиная с первой. Символы табуляции во входном файле развертываются в соответствующее количество пробелов. Если задан любой нецифровой символ, он трактуется как символ табуляции во входном файле.

-F

Применять символы перехода к новой странице (по умолчанию переход на новую страницу осуществляется при помощи последовательности переводов строк).

-h заголовок

Использовать заданный заголовок (по умолчанию в качестве заголовка выступает имя исходного файла).

-i[символ][число]

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

-l число_строк

Установка длины страницы (по умолчанию 66 строк).

-m

Слияние и печать всех файлов параллельно, по одному в столбце. Реализация должна поддерживать слияние по крайней мере девяти файлов.

-n[символ][ширина]

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

-o отступ

Установка отступа слева на заданное число позиций. Количество позиций в каждой строке есть сумма ширины и отступа.

-p

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

-r

Подавление диагностических сообщений при ошибках открытия файлов.

-s[символ]

Разделение столбцов единственным символом (по умолчанию - табуляцией), а не соответствующим количеством пробелов.

-t

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

-w ширина

Установка ширины строки (по умолчанию - 72 позиции). Эта опция действует только при печати в несколько столбцов.

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

ls -a | pr -2 -a -l 24 -n' '3 -h "Список\
файлов каталога $(pwd)"

Начальный фрагмент результата может выглядеть так, как показано в листинге 6.5.

2003-10-17 17:24 Список файлов каталога
/var/tmp Page 1
1 . 2 ..
3 Blank.ReS 4 Make45.ReS
5 Make46.ReS 6 from_cvs
7 gcc-20032204 8 gcc-3.4-16-jun-2003
9 htdocs 10 rpm-tmp.29785
. . .

Для подсчета числа символов, слов и строк в файлах служит утилита wc:

wc [-c | -m] [-w] [-l] файл ...

Опция -c предписывает подсчитывать байты, -m - символы, -w - слова, -l - строки. По умолчанию подсчитываются символы, слова и строки. Если в командной строке задано несколько файлов, помимо результатов для каждого из них выводятся также суммарные показатели.

Рассмотрим пример. Предположим, что результаты, показанные в листинге 6.3, сохранены в файле ascii.od. Тогда команда, приведенная в листинге 6.6, выдаст результат, показанный в листинге 6.7.

wc ascii.od
9 137 575 ascii.od

Иными словами, в указанном файле 575 символов, 137 слов, расположенных на 9 строках.

Служебная программа sort

sort [-m] [-o выходной_файл] [-bdfinru] 
[-t символ] [-k определение_ключа] ... 
[файл ...]

sort -c [-bdfinru] [-t символ] 
[-k определение_ключа] [файл]

в зависимости от заданных опций выполняет одно из трех возможных действий:

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

    Следующие опции управляют порядком работы утилиты sort.

    -c

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

    -m

    Слияние исходных файлов, которые предполагаются отсортированными.

    -o выходной_файл

    Результат направляется не на стандартный вывод, а в выходной_файл, который может совпадать с одним из исходных.

    -u

    Опция уникальности: из всех совпадающих строк выводить только одну, при наличии опции -c контролировать отсутствие строк с совпадающими ключами сортировки.

    Следующие опции изменяют подразумеваемый способ сравнения. Если они употреблены независимо от определения ключей сортировки, то диктуемые ими правила действуют глобально, на все ключи.

    -d

    "Словарный" порядок: при сравнении являются значимыми только буквы, цифры и пробельные символы.

    -f

    При сравнении преобразовывать малые буквы в большие.

    -i

    При сравнении игнорировать непечатные символы.

    -n

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

    -r

    Заменить результат сравнения на противоположный.

    Следующие опции изменяют трактовку разделителей полей.

    -b

    Игнорировать начальные пробельные символы при определении начала и конца ключей сортировки. Если опция -b указана перед первой опцией -k, она воздействует на все ключи, снабженные опцией -k.

    -t символ

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

    Ключи сортировки определяются с помощью опции -k. Определение ключа имеет следующий формат:

    начало_поля[тип][,конец_поля[тип]]

    Ключевое поле охватывает все позиции - от начальной до конечной, которые задаются, соответственно, как

    номер_поля[.первый_символ]
    номер_поля[.последний_символ]

    Тип - это один из модификаторов b, d, f, i, n, r. Модификатор b ведет себя аналогично опции -b, но применяется только к той границе ключа, после которой он указан. Остальные модификаторы также аналогичны одноименным опциям, но воздействуют на ключ в целом.

    Если указано несколько ключей сортировки (согласно стандарту, реализацией должно поддерживаться по крайней мере девять вхождений опции -k ), то более поздние используются только в случае равенства более ранних. Если значения ключей сортировки двух строк совпадают, строки упорядочиваются с учетом всех символов.

    Приведем примеры использования служебной программы sort. Автор хранит данные о собственном кровяном давлении в виде файла pp.txt, содержащего пять столбцов: дата, время, верхнее давление, нижнее давление, пульс (см. листинг 6.8). Естественно, строки добавляются к файлу в хронологическом порядке. Чтобы упорядочить их по убыванию верхнего давления (третий столбец ), можно воспользоваться командой, показанной в листинге 6.9. Возможный результат приведен в листинге 6.10.

    12.05.2000 17:30 200 125 120
    15.05.2000 17:00 130 80 70
    17.05.2000 10:30 150 90 70
    17.05.2000 21:45 154 99 74
    19.05.2000 10:05 158 83 89
    21.05.2000 21:00 161 104 64
    22.05.2000 21:00 147 104 69
    . . .
    sort -r -k 3,3 pp.txt
    12.05.2000 17:30 200 125 120
    18.11.2000 19:30 172 107 68
    04.07.2002 09:00 170 98 85
    30.10.2001 13:00 168 94 88
    27.12.2000 20:30 166 98 69
    23.05.2002 10:00 166 104 56
    22.05.2002 10:00 166 103 57
    23.10.2001 11:00 165 88 88
    . . .

    Если ключи сортировки равны, строки упорядочиваются с учетом всех байт. В рассматриваемом примере это не очень удобно, поскольку они начинаются с номера дня в месяце. Более естественно либо расширить ключ сортировки путем охвата полей нижнего давления и пульса, либо сделать эти поля дополнительными ключами. Оба варианта показаны в листинге 6.11, а результат сортировки - в листинге 6.12. Отметим, что второй вариант предпочтительнее, поскольку он устойчив к ошибкам выравнивания столбцов.

    # Первый вариант - ключ сортировки покрывает
    # несколько полей sort -r -k 3,5 pp.txt
    # Второй вариант - используется несколько 
    # ключей сортировки 
    # sort -n -r -k 3,3 -k 4,4 -k 5,5 pp.txt
    12.05.2000 17:30 200 125 120
    18.11.2000 19:30 172 107 68
    04.07.2002 09:00 170 98 85
    30.10.2001 13:00 168 94 88
    23.05.2002 10:00 166 104 56
    22.05.2002 10:00 166 103 57
    27.12.2000 20:30 166 98 69
    23.10.2001 11:00 165 88 88
    . . .

    Предположим теперь, что данные о давлении и пульсе разбиты по годам и в уже отсортированном виде хранятся в файлах pp2000.sorted, pp2001.sorted и т.д. Для их слияния можно воспользоваться командной строкой или циклом из листинга 6.13. Опять-таки второй вариант предпочтительнее по причине его устойчивости к количеству исходных файлов.

    # Первый вариант слияния файлов 
    # sort -m -o pp.sorted -n -r -k 3,3 -k 4,4
    # -k 5,5 pp2*.sorted
    # Второй вариант слияния файлов
    > pp.sorted
    for f in pp2*.sorted
       do
          sort -m -o pp.sorted -n -r -k 3,3\
          -k 4,4 -k 5,5 $f pp.sorted
       done

    Проверить правильность слияния можно с помощью команд, показанных в листинге 6.14.

    name=pp.sorted
    if sort -c -r -n -k 3,3 -k 4,4 -k 5,5 $name
    then
       echo Данные в файле $name отсортированы\
        верно
    elif
       echo Данные в файле $name отсортированы\
        неверно
    fi

    Содержимое файлов f1 и f2 сортирует команда (см. листинг 6.15) , применяющая в качестве ключа второй непробельный символ второго поля.

    sort -k 2.2b,2.2b f1 f2

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

    sort -t ':' -k 3,3n /etc/passwd

    Выдать содержимое уже отсортированного файла   f.sorted, выводя лишь первую из строк с одинаковыми третьими полями, поможет комбинация опций -m и -u (см. листинг 6.17).

    sort -um -k 3.1,3 f.sorted

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

    diff [-c | -e | -f | -C число] [-br] 
         файл1 файл2

    Утилита diff сравнивает содержимое исходных файлов и выдает на стандартный вывод список изменений, которые необходимо произвести, чтобы преобразовать файл1 в файл2. Она поддерживает следующие опции.

    -b

    Игнорировать пробельные символы в конце строк; остальные цепочки пробельных символов считать равными.

    -c

    Производить вывод в формате, обеспечивающем три строки контекста.

    -C число

    Производить вывод в формате, обеспечивающем заданное число строк контекста.

    -e

    Производить вывод в формате, пригодном для подачи на вход редактора ed и преобразования файла1 в файл2.

    -f

    Производить вывод в альтернативном формате, напоминающем -e, но в обратном порядке и не предназначенном для подачи на вход редактора ed.

    -r

    Если файл1 и файл2 представляют собой каталоги, применять утилиту diff рекурсивно к одноименным и однотипным обычным файлам и каталогам. Если только один из файлов является каталогом, в нем сравнивается "тезка" другого исходного файла.

    В качестве примера использования служебной программы diff сравним две версии бинарных утилит (см. листинг 6.18). Начальный фрагмент результата показан в листинге 6.19. Видно, что различия, по сути, сводятся к добавлению новых файлов.

    diff -r binutils-2_14 binutils-2_14-branch
    diff -r binutils-2_14/bfd/version.h binutils-2_14-branch/bfd/version.h
    1c1
    < #define BFD_VERSION_DATE 20030612
    ---
    > #define BFD_VERSION_DATE 20031007
    Only in binutils-2_14-branch/binutils: ChangeLog
    Only in binutils-2_14-branch/binutils: arlex.c
    Only in binutils-2_14-branch/binutils: deflex.c
    Only in binutils-2_14-branch/binutils: rclex.c
    Only in binutils-2_14-branch/binutils: syslex.c
    Only in binutils-2_14-branch: config.guess
    Only in binutils-2_14-branch/gas: ChangeLog
    Only in binutils-2_14-branch/gas/config: tc-ns32k.c
    Only in binutils-2_14-branch/gas: configure
    Only in binutils-2_14-branch/gas: configure.in
    Only in binutils-2_14-branch/gas: itbl-lex.c
    . . .

    Если нужно проверить два файла на совпадение, предпочтительнее воспользоваться не утилитой diff, а более простой и быстрой служебной программой cmp:

    cmp [-l | -s] файл1 файл2

    По умолчанию cmp выдает на стандартный вывод номера байта и строки, где встретилось первое отличие. Опция -l предписывает выдавать все отличия в виде номеров и содержимого (восьмеричного) байт. По опции -s отличия не выдаются, возвращается лишь соответствующий код завершения (0 - файлы совпадают, 1 - файлы различаются, больше единицы - зафиксирована ошибка).

    Если в продолжение примера с утилитой diff сравнить две версии файла version.h посредством cmp (см. листинг 6.20), результат будет выглядеть так, как показано в листинге 6.21.

    cmp -l binutils-2_14/bfd/version.h\
      binutils-2_14-branch/bfd/version.h
    30 60 61
    31 66 60
    32 61 60
    33 62 67

    Еще одно средство выявления различий (и, кроме того, совпадений) текстовых файлов - служебная программа comm:

    comm [-123] файл1 файл2

    Она читает файл1 и файл2 (их содержимое должно быть отсортировано в лексикографическом порядке) и выводит результаты в три столбца:

  • строки, входящие только в файл1;
  • строки, входящие только в файл2;
  • строки, входящие в оба файла.
  • Опции -1, -2 или -3 подавляют вывод соответствующих столбцов. Так, comm -12 выводит строки, общие для обоих файлов, comm -23 - имеющиеся только в файле1, comm -123 не выводит ничего.

    В стандарте POSIX-2001 приведен следующий пример использования служебной программы comm. Пусть файл xcu содержит отсортированный список утилит, вошедших в данный стандарт, файл xpg3 - аналогичный список для спецификации X/Open Portability Guide, Issue 3, а файл svid89 представляет собой отсортированный список утилит из System V Interface Definition Third Edition. Тогда конвейер, показанный в листинге 6.22, выдаст список служебных программ, фигурирующих в спецификациях XPG3 и SVID, но не вошедших в стандарт IEEE Std1003.1-2001.

    comm -12 xpg3 svid89 | comm -23 - xcu

    Для контроля целостности файлов (не являющегося, впрочем, криптостойким), предназначена служебная программа

    cksum [файл ...]

    Для каждого файла - аргумента она выдает на стандартный вывод подсчитанную по стандарту ISO/IEC 8802-3:1996 (Ethernet) контрольную сумму, количество байт и имя.

    Применение утилиты cksum - полезный элемент оформления поставляемых приложений.

    Регулярные выражения

    Понятие регулярного выражения (РВ) - одно из важнейших для программ обработки текстовых файлов. Согласно стандарту POSIX-2001, регулярное выражение - это шаблон, служащий для выборки определенных цепочек символов из множества подобных цепочек. Говорят, что выбранные цепочки удовлетворяют РВ (успешно с ним сопоставляются).

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

    Стандарт накладывает на сложность (полных) РВ единственное ограничение: реализация должна поддерживать любое РВ, длина которого не превышает 256 байт.

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

    Различают базовые (БРВ) и расширенные регулярные выражения (РРВ). В большинстве случаев используются БРВ, они и будут описаны в первую очередь.

    Минимальным элементом БРВ являются односимвольные БРВ, т. е. БРВ, которым удовлетворяют цепочки из одного символа. Односимвольные БРВ строятся по следующим правилам.

  • Обычный символ (не входящий в перечень из следующего пункта) - это односимвольное   БРВ, которое успешно сопоставляется с указанным символом.
  • Если за символом \ следует любой специальный символ, то последний теряет свой специальный смысл. Данная комбинация определяет односимвольное   БРВ, успешно сопоставляющееся со специальным символом. Специальными символами являются:
    . [ \
    Эти символы имеют специальный смысл, за исключением случая, когда они заключены в квадратные скобки.
    *
    Символ имеет специальный смысл, если он не заключен в квадратные скобки или не является первым символом БРВ.
    ^
    Символ имеет специальный смысл в начале полного БРВ или в квадратных скобках сразу после открывающей скобки.
    $
    Символ имеет специальный смысл в конце полного БРВ.
  • Точка ( .) - односимвольное   БРВ, успешно сопоставляющееся с любым символом.
  • Непустая цепочка символов, заключенная в квадратные скобки [ ] представляет собой односимвольное   БРВ, которое успешно сопоставляется с любым символом из этой цепочки. Если цепочка начинается с символа ^, то БРВ успешно сопоставляется с любым не входящим в данный набор символом.
  • Внутри квадратных скобок можно использовать также конструкции, описываемые ниже.

    Диапазон символов можно задать с помощью знака -. Например, БРВ   [0-9] эквивалентно [0123456789]. Минус теряет свой специальный смысл, если он стоит в начале (допустимо после ^ ) или в конце цепочки символов в квадратных скобках либо использован в качестве границы диапазона. Так, БРВ   [%--] успешно сопоставляется со всеми символами от процента до минуса включительно.

    Закрывающая квадратная скобка не рассматривается как окончание цепочки символов, если она стоит вначале (быть может, после ^ ). Таким образом, БРВ   []a-f] определяет либо закрывающую квадратную скобку, либо любой символ от a до f включительно.

    Стандарт POSIX-2001 предусматривает поддержку классов символов (таких, например, как буквы, цифры, знаки пунктуации, пробельные символы, управляющие символы и т.д.). Класс символов задается своим именем, заключенным в квадратные скобки с двоеточиями. Должны поддерживаться по крайней мере следующие классы:

    [:alnum:] [:cntrl:] [:lower:] [:space:]
    [:alpha:] [:digit:] [:print:] [:upper:]
    [:blank:] [:graph:] [:punct:] [:xdigit:]

    Например, БРВ [^[:alnum:]] удовлетворяют символы, отличные от букв и цифр.

    В некоторых языках элемент алфавитного сравнения может задаваться несколькими символами. Такие элементы следует заключать в квадратные скобки с точками: [[.ch.]].

    Если определены классы элементов, эквивалентных при алфавитном сравнении, то подобный класс можно задать, заключив любой его элемент в квадратные скобки со знаками равенства: ([= и =]).

    Таковы правила построения заключенных в квадратные скобки односимвольных БРВ.

    Построение многосимвольных   БРВ из односимвольных регламентируется следующим образом.

  • В состав БРВ могут входить подвыражения ( в том числе вложенные), заключенные в экранированные круглые скобки: \( и \). Подвыражение в скобках успешно сопоставляется с теми же цепочками, что и без скобок.
  • На подвыражения допускаются обратные ссылки вида \n (где n - цифра от 1 до 9). Обратной ссылке удовлетворяет такая же цепочка символов, что была успешно сопоставлена подвыражением, открывающимся экранированной скобкой номер n, считая от начала полного БРВ. Например, БРВ \(.\)\1 удовлетворяют пары одинаковых символов.
  • За односимвольными БРВ, подвыражениями и обратными ссылками может следовать звездочка. Такая конструкция успешно сопоставляется с любым (в частности, с нулевым) числом последовательных вхождений упомянутых БРВ. Например, шаблону \(.*\)\1 удовлетворяют пары стоящих рядом одинаковых цепочек символов.
  • За односимвольными   БРВ, подвыражениями и обратными ссылками может следовать запись вида \{m\}, \{m,\} или \{m,n\} , называемая интервальным выражением. Такая конструкция успешно сопоставляется с определенным числом вхождений упомянутых БРВ. Значения m и n должны удовлетворять неравенствам
  • Конкатенация компонентных БРВ есть БРВ, которое успешно сопоставляется с конкатенацией цепочек, удовлетворяющих каждому из компонентных БРВ.
  • На БРВ можно наложить ограничение, чтобы успешно сопоставленная   подцепочка символов примыкала к одной или обоим границам анализируемой цепочки (произвести фиксацию границ ). Если в начале БРВ стоит символ ^ ( фиксатор начала ), то сопоставление должно выполняться с начала цепочки .   Если в конце БРВ стоит символ $ ( фиксатор конца ), то сопоставление должно выполняться до конца цепочки . Например, БРВ ^\(.*\)\1$ успешно сопоставляется с цепочками символов, состоящими из двух одинаковых подцепочек.
  • Операции построения многосимвольных   БРВ описаны выше в порядке убывания приоритетов. Наибольшим приоритетом обладают подвыражения и обратные ссылки, далее следуют повторители - звездочка и интервальные выражения, затем конкатенация и, наконец, фиксация границ.

    Опишем отличия расширенных регулярных выражений (РРВ) от базовых.

  • В число специальных символов дополнительно входят круглые скобки, открывающая фигурная скобка, а также символы ?, |.
  • Для группирования (выделения подвыражений ) используются неэкранированные круглые скобки.
  • Отсутствуют обратные ссылки.
  • Добавлены два повторителя: плюс обозначает любое ненулевое число последовательных вхождений; знак вопроса - нуль или одно вхождение.
  • Для выделения интервальных выражений используются неэкранированные фигурные скобки.
  • Введена операция логического ИЛИ, которая обозначается символом | и имеет низший приоритет. Результат сопоставляется с цепочками, удовлетворяющими хотя бы одному из операндов. Например, РРВ   a((bc)|d) успешно сопоставляется и с цепочкой символов   "abc", и с "ad". Односимвольные РРВ, разделенные символом | и заключенные в круглые скобки, трактуются как односимвольные.
  • Таковы правила построения и обработки регулярных выражений, зафиксированные в стандарте POSIX-2001. Отметим, что базовые регулярные выражения не являются подмножеством расширенных, хотя число специфических особенностей БРВ невелико.

    Использование регулярных выражений

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

    grep [-E | -F] [-c | -l | -q] [-insvx] 
         -e список_шаблонов ... 
    	 [-f файл_шаблонов] ... [файл ...]
    
    grep [-E | -F] [-c | -l | -q] [-insvx]
         [-e список_шаблонов ...] 
         -f файл_шаблонов ... [файл ...]
    
    grep [-E | -F] [-c | -l | -q] [-insvx] 
         список_шаблонов [файл ...]

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

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

    По умолчанию шаблоны трактуются как базовые регулярные выражения. Опция -E предписывает переход к расширенным РВ, а опция -F означает, что в качестве шаблонов выступают цепочки символов (и, следовательно, процесс сопоставления существенно упрощается).

    Опции -c, -l, -q и -n влияют на выдачу результатов. По умолчанию на стандартный вывод выдаются строки исходных файлов, в которых присутствуют успешно сопоставленные   цепочки символов. Опция -c предписывает выдавать только общее число подобных строк, -l - только имена файлов, где имеются успешно сопоставленные строки, -q - только код завершения (0 - есть успешно сопоставленные строки), -n требует ставить ее номер в исходном файле перед каждой выводимой строкой (нумерация строк начинается с 1).

    Опции -i, -v и -x воздействуют на процесс сопоставления: -i предписывает не различать при сопоставлении большие и малые буквы, -v - выбирать строки, не удовлетворяющие ни одному из заданных шаблонов, -x - рассматривать только строки, все символы которых участвуют в успешном сопоставлении с одним из шаблонов.

    Опция -s подавляет выдачу диагностических сообщений о том, что исходный файл не существует или не доступен на чтение.

    Отметим, что опции -F и -q, каждая по-своему, ускоряют работу служебной программы grep: -F упрощает сопоставление, -q позволяет завершить действие после первого успешного сопоставления (и не обрабатывать оставшиеся строки и/или файлы). Сочетание опций -q и -s позволяет также более свободно задавать исходные файлы, не заботясь об их существовании и доступности.

    Рассмотрим примеры использования утилиты grep. Для выборки пустых строк из файла стандартного ввода пригодны два шаблона:

    grep ^$
    grep -v .

    Если нужно выбрать строки, имеющие вид abc или def, можно воспользоваться одной из трех команд:

    grep -E '^abc$|^def$'
    grep -F -x 'abc
    def'

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

    grep -i '^[^C].* CALL ' *.for

    В командных файлах для обработки текстов часто используется потоковый редактор   sed:

    sed [-n] сценарий [файл ...]
    sed [-n] [-e сценарий] ... 
        [-f файл_сценария] ... [файл ...]

    Редактор sed читает указанные текстовые файлы (по умолчанию - стандартный ввод), выполняет редактирование в соответствии с командами сценария и записывает результат на стандартный вывод. Смысл опций -e и -f аналогичен утилите grep. Опция -n подавляет подразумеваемый вывод и предписывает выдавать только явно отобранные строки.

    Сценарий для sed состоит из редактирующих команд (каждая на отдельной строке), имеющих следующий формат:

    [адрес [, адрес]] функция [аргумент ...]

    Функция имеет здесь однобуквенное обозначение.

    В нормальном режиме sed циклически выполняет следующие действия:

  • Добавляет входную строку в буфер. Обычно буфер пуст, если только предыдущий цикл не завершился командой D.
  • Применяет к буферу последовательно все команды сценария, адреса в которых позволяют их применить.
  • Если не указана опция -n, копирует буфер на стандартный вывод, добавив в конце перевод строки. Очищает буфер.
  • Некоторые команды используют хранилище, чтобы запомнить весь буфер или его часть для последующего применения.

    Адрес в редактирующей команде sed - это либо десятичное число, означающее номер входной строки в совокупности входных файлов, либо символ $, который обозначает последнюю входную строку, либо контекстный адрес, имеющий вид /базовое_регулярное_выражение/. Контекстный адрес задает первую (начиная с текущей) из строк, успешно сопоставленных с БРВ при движении вперед.

    Командная строка без адреса применима к любому буферу, командная строка с одним адресом - к буферу с соответствующим адресом, командная строка с двумя адресами - к буферам с адресами в диапазоне от первого до второго включительно; затем процесс повторяется, начиная с первой строки вслед за выбранным диапазоном.

    Перечислим команды редактора sed. В скобках указывается максимальное число допустимых адресов для каждой функции.

    Аргумент текст состоит из одной или более строк. Все строки, кроме последней, заканчиваются на \, чтобы экранировать символ перевода строки.

    (2){ функция
    функция
    ...
    }

    Выполнить заданную последовательность функций.

    (1)a\
    текст

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

    (2)b [метка]

    Перейти к команде :, содержащей метку. Если метка пуста, перейти на конец сценария.

    (2)c\ текст

    Заменить. Удалить содержимое буфера. При 0 или 1 адресе или в конце двухадресного диапазона вывести текст. Начать новый цикл.

    (2)d

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

    (2)D

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

    (2)g

    Заменить содержимое буфера содержимым хранилища.

    (2)G

    Добавить к содержимому буфера содержимое хранилища.

    (2)h

    Заменить содержимое хранилища содержимым буфера.

    (2)H

    Добавить к содержимому хранилища содержимое буфера.

    (1)i\
    текст

    Вставить. Вывести текст.

    (2)l

    Вывести буфер, заменяя непечатные символы на пары символов ASCII и разбивая длинные строки.

    (2)n

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

    (2)N

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

    (2)p

    Скопировать буфер на стандартный вывод.

    (2)P

    Скопировать начальный сегмент буфера (до первого перевода строки) на стандартный вывод.

    (1)q

    Выйти. Перейти на конец сценария. Нового цикла не начинать.

    (2)r ч_файл

    Прочитать содержимое ч_файла. Поместить его на стандартный вывод перед чтением следующей входной строки.

    (2)s/БРВ/замена/флаги

    Подставить замену вместо фрагментов буфера, отождествленных с БРВ. Флаги могут быть опущены или иметь следующие значения:

  • число n - заменить n-е вхождение БРВ ;
  • g - заменить все вхождения БРВ, а не только первое;
  • p - если замена произошла, вывести содержимое буфера;
  • w з_файл - если замена произошла, добавить содержимое буфера к з_файлу.
  • Вместо символа в замене подставляется цепочка, отождествленная с БРВ.

    (2)t [метка]

    Проверить. Перейти к команде :, содержащей метку, если со времени последнего чтения входной строки или последнего выполнения команды t в буфере производились подстановки. Если метка пуста, перейти на конец сценария.

    (2)w з_файл

    Записать. Добавить содержимое буфера к з_файлу.

    (2)x

    Обменять содержимое буфера и хранилища.

    (2)y/цепочка1/цепочка2/

    Заменить все символы буфера, содержащиеся в цепочке1, на соответствующие символы цепочки2. Длины цепочек должны совпадать.

    (2)! функция

    Отрицание. Применить функцию (или группу, если функция начинается с { ) только к строкам, не соответствующим адресам.

    (0): метка

    Не делает ничего. Содержит лишь метку, на которую может быть осуществлен переход командами t или b.

    (1)=

    Вывести в качестве отдельной строки номер текущей строки.

    (0)

    Пустая команда.

    (0)#

    Управляющий комментарий. Если сценарий начинается с символов #n, подразумеваемый вывод подавляется (что эквивалентно опции -n в командной строке). В остальных случаях игнорировать # и остаток строки.

    Последовательность символов \n успешно сопоставляется с переводом строки. Явный символ перевода строки не должен использоваться в БРВ контекстных адресов и функции замены.

    Приведем примеры использования потокового редактора   sed. В процессе загрузки ОС Linux выполняются командные строки, аналогичные показанным в листинге 6.23.

    map=`basename $map | sed -e s/^auto_home/auto.home/ -e s/^auto_mnt/auto.mnt/`
    cat /etc/auto.master | grep -v '^+' | sed -e '/^#/d' -e '/^$/d'

    Первая из них заменяет подчеркивание на точку в именах файлов, обслуживающих автомонтирование файловых систем, вторая отсеивает строки файла auto.master, начинающиеся с символа + (это делает grep -v ), комментарии (строки, начинающиеся символом #) и пустые строки.

    Следующий вызов sed (см. листинг 6.24) сжимает несколько идущих подряд пустых строк в одну.

    sed -n '
    p
    /^$/ {
    # Текущая строка - пустая.
    # Добавляем следующие строки к буферу, 
    # пока он остается пустым.
    # Тем самым игнорируются "лишние" пустые 
    # строки.
    :Empty
       n
       /^$/ b Empty
    # Добавленная строка оказалась непустой. 
    # Выведем ее.
       p
    }
    '

    Любопытно сопоставить приведенный нами сценарий с примером, включенным в текст стандарта POSIX-2001 (см. листинг 6.25). Наш вариант явно проще и короче.

    sed -n '
    # Выведем непустые строки
    /./ {
       p
       d
    }
    # Выведем одну пустую строку, затем 
    # проанализируем следующие.
    /^$/ p
    # Прочитаем следующую строку, отбросим 
    # оставшийся перевод строки (пустую строку)
    # и вернемся к проверке пустой строки.
    :Empty
    /^$/ {
       N
       s /.//
       b Empty
    }
    # Выведем непустую строку, затем вернемся к 
    # поиску первой пустой.
    p
    '

    Еще одно популярное средство обработки текстовых файлов - служебная программа awk:

    awk [-F РРВ] [-v присваивание] ... 
        программа [аргумент ...]
    awk [-F РРВ] -f программный_файл ... 
        [-v присваивание] ... [аргумент ...]

    Утилита awk выполняет программы, написанные на одноименном языке программирования, специально предназначенном для обработки текстов. Программа на языке awk представляет собой последовательность шаблонов и соответствующих действий, которые выполняются при чтении входных данных, успешно сопоставляющихся с шаблоном. Оператор шаблон-действие имеет вид

    шаблон { действие }

    Ввод для awk делится на записи, разделяемые специальным символом. По умолчанию это перевод строки; в таком случае awk обрабатывает ввод построчно. Разделитель записей можно изменить, переопределив переменную   RS. Каждая запись делится на поля, ограниченные разделителями полей (по умолчанию - пробелами или табуляциями). Любой из них можно изменить, переопределив переменную   FS или указав опцию -F с аргументом - расширенным регулярным выражением (РРВ). Поля исходных строк доступны по именам $1, $2,...; $0 - вся входная строка.

    Каждая исходная строка сопоставляется с каждым из шаблонов; в случае успеха выполняются указанные действия. После сопоставления со всеми шаблонами вводится следующая строка и процесс сопоставления повторяется. Может быть опущен либо шаблон, либо действие, но не оба вместе. Если для данного шаблона не указаны действия, то строка просто копируется на стандартный вывод. Если для действия не определен шаблон, то оно будет выполняться для каждой входной строки. Строки, которые не удалось сопоставить ни одному шаблону, игнорируются.

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

  • if ( условие ) оператор [ else оператор ];
  • while ( условие ) оператор;
  • for ( выражение; условие; выражение ) оператор;
  • break;
  • continue;
  • { [ оператор ] ... };
  • переменная = выражение # оператор присваивания;
  • print [ список_выражений ] [> выражение ];
  • printf формат [, список_выражений ] [> выражение ];
  • next # пропустить оставшиеся шаблоны и перейти к следующей строке;
  • exit # пропустить оставшиеся строки.
  • Операторы завершаются точкой с запятой, переводом строки или правой скобкой. Пустой список_выражений означает всю строку. Выражения строятся из цепочек символов и чисел с помощью операций +, -, *, /, %, ^ (возведение в степень) и конкатенации (обозначается пробелом). В них также можно использовать операции из языка C: ++, --, +=, -=, *=, /=, %=, ^=, ?: (условное выражение ). Переменные инициализируются пустыми цепочками, могут быть скалярами, элементами массива (обозначается x[i] ) или полями. Индексами массива служат любые (не обязательно числовые) цепочки символов, что позволяет реализовать разновидность ассоциативной памяти. Цепочки символов заключаются в двойные кавычки (").

    Оператор   print выдает свои аргументы на стандартный вывод (или в файл, если присутствует часть >выражение), разделяя их текущим разделителем полей и завершая каждую запись выходным разделителем записей. Оператор   printf делает то же, но под управлением формата.

    Язык awk содержит большое число встроенных функций. Кратко опишем их.

    Математические функции atan2 (y, x), cos (x), sin (x), exp (x), log (x), sqrt (x) не нуждаются в пояснениях. Функция int (x) отбрасывает дробную часть своего аргумента, rand () возвращает псевдослучайное число в диапазоне от

    В число функций, оперирующих цепочками символов, входят gsub (РРВ, замена[, цепочка]) и sub (РРВ, замена[, цепочка]) - соответственно, глобальная и однократная замена вхождений РРВ в $0 или цепочку, по аналогии с командой s редактора sed и ее флагом g; index ( цепочка, подцепочка ) - поиск подцепочки в цепочке ; length [([цепочка])] - вычисление длины цепочки-аргумента или $ ); match> (цепочка, РРВ) - поиск вхождения РРВ в цепочку с установкой значений переменных   RSTART и RLENGTH (см. далее); split (цепочка, массив[, РРВ-разделитель]) - расщепление цепочки по полям в элементы массива); sprintf (формат, выражение, выражение, ...) - формирование цепочки символов средствами форматного вывода; substr (цепочка, m[, n]) - выделение n-символьной подцепочки, начинающейся с позиции m ; tolower (цепочка) - приведение к строчным буквам; toupper (цепочка) - приведение к прописным буквам.

    В языке awk имеются также группа функций ввода/вывода и функции общего назначения. Функция close (выражение) закрывает файл или канал, поименованный заданным выражением, getline [переменная] обеспечивает чтение записи из текущего входного файла (возможно использование конвейера вида выражение   | getline [переменная] и перенаправление ввода getline [переменная] < выражение ), system (выражение) - выполнение команды, заданной выражением.)

    Язык awk допускает определение пользовательских функций, для чего служит конструкция

    function имя_функции ([аргумент, ...]) 
        { операторы }

    Шаблон в языке awk - это произвольная логическая комбинация, составленная с помощью операций !, ||, и скобок из расширенных регулярных выражений и выражений сравнения. РРВ обрамляются символами /. Отдельное РРВ в шаблоне сопоставляется со всей строкой. РРВ допускаются и в выражениях сравнения. Шаблон может состоять из двух шаблонов, разделенных запятой; указанные действия выполняются для всех строк между строкой, удовлетворяющей первому шаблону, и строкой, удовлетворяющей второму.

    Выражение сравнения - одна из следующих конструкций:

    выражение опер_сопост РРВ
    выражение опер_сравн выражение

    Здесь опер_сравн - любая из шести операций сравнения языка C, опер_сопост - это ~ (успешно сопоставляется) или !~ (не сопоставляется).

    Условие - арифметическое выражение, выражение сравнения или их логическая комбинация.

    Для выполнения каких-либо действий перед чтением первой или после чтения последней исходной строки определены специальные шаблоны BEGIN и END. Шаблон BEGIN следует указывать первым, END - последним.

    Присваивания, заданные в командной строке с помощью опции -v, выполняются до начала интерпретации awk-программы (в частности, до действий, ассоциированных с шаблоном BEGIN ). Например, для использования символа c в качестве разделителя полей можно указать в командной строке -v 'FS = c'.

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

    Перечислим специальные переменные awk.

    ARGC

    Число элементов в массиве ARGV.

    ARGV

    Массив аргументов командной строки awk, исключая опции и программы.

    CONVFMT

    Формат для преобразования чисел в цепочки символов (кроме операторов вывода, где используется переменная   OFMT, см. далее). По умолчанию - %.6g.

    ENVIRON

    Массив, представляющий окружение. Индексами служат цепочки символов, совпадающих с именами переменных окружения.

    FILENAME

    Имя файла, из которого в данный момент производится ввод.

    FNR

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

    FS

    РРВ - разделитель полей во входных данных, по умолчанию - пробел.

    NF

    Количество полей в текущей записи.

    NR

    Порядковый номер текущей записи, считая от начала обработки исходных данных.

    OFMT

    Формат вывода чисел, по умолчанию %.6g.

    OFS

    Разделитель полей при выводе, по умолчанию - пробел.

    ORS

    Разделитель записей при выводе, по умолчанию - перевод строки.

    RLENGTH

    Длина успешно сопоставленной функцией match() цепочки символов.

    RS

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

    RSTART

    Начальная позиция успешно сопоставленной функцией match()   цепочки символов (считая от 1).

    SUBSEP

    Цепочка символов - разделитель индексов многомерных массивов; подразумеваемое значение зависит от реализации.

    Приведем примеры использования утилиты awk. Сложить числа, стоящие в первом столбце исходного файла, вывести сумму и среднее арифметическое позволяет awk-программа, показанная в листинге 6.26.

    { s += $1 } END { print "Сумма:", s, 
    " Среднее арифметическое:", s/NR }

    Командная строка из листинга 6.27, служит для вывода тех строк файла f1.txt, у которых первое поле не совпадает с первым полем предыдущей строки.

    awk '$1 != prev { print; prev = $1 }' f1.txt

    Чтобы распечатать файл f2.txt, вставляя после слова "Page" номера страниц (начиная с первой), можно воспользоваться awk-программой (предполагается, что она помещена в файл prog.awk ) и командной строкой, представленными, соответственно, в листингах 6.28 и 6.29.

    /Page/ { $2 = n++ } { print }
    awk -f prog.awk -v 'n=1' f2.txt

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

    { for (i = NF; i > 0; --i) print $i }

    Промоделировать работу утилиты echo можно с помощью awk-программы (см. листинг 6.31).

    BEGIN {
       for (i = 1; i < ARGC; ++i)
       printf ("%s%s", ARGV [i], 
               i == ARGC - 1 ? "\n" : " ")
    }

    Следующая awk-программа (см. листинг 6.32) позволяет разложить список поиска, хранящийся в переменной окружения PATH, по элементам массива.

    BEGIN {
       n = split (ENVIRON ["PATH"], path, ":")
       for (i = 1; i <= n; ++i)
          print path [i]
    }

    В листинге 6.33 приведен фрагмент командного файла, выполняемого при выключении системы. Здесь можно обратить внимание на разные виды экранирования. (Третье поле в выдаче mount - это точка монтирования.)

    # Перемонтируем на чтение все, что еще остается смонтированным.
    mount | awk '/( \/ |^\/dev\/root)/ { print $3 }' | while read line; do
       mount -n -o ro,remount $line
    done

    Отметим, что в POSIX-2001 стандартизована весьма развитая версия awk, входной язык которой приближен к языку C.

    На уровне функций работа с регулярными выражениями поддержана семейством regex (см. листинг 6.34).

    #include <regex.h>
    int regcomp (regex_t *restrict preg, 
        const char *restrict pattern, int cflags);
    int regexec (const regex_t *restrict preg, 
        const char *restrict string, 
        size_t nmatch, regmatch_t 
    	pmatch [restrict], int eflags);
    void regfree (regex_t *preg);
    size_t regerror (int errcode, const regex_t
        *restrict preg, char *restrict errbuf, 
    	size_t errbuf_size);

    Первый член этого семейства, функция regcomp(), компилирует регулярное выражение, заданное аргументом pattern, и помещает результат компиляции в структуру типа regex_t, на которую указывает аргумент preg. Эта структура, описанная в заголовочном файле <regex.h>, должна содержать про крайней мере поле

    size_t re_nsub; 
    /* Число заключенных в скобки подвыражений */

    Третий аргумент функции regcomp(), cflags, задается как побитное ИЛИ следующих флагов:

    REG_EXTENDED

    Использовать расширенные регулярные выражения (подразумеваемый тип регулярных выражений - базовые ).

    REG_ICASE

    При сопоставлении не различать большие и малые буквы.

    REG_NOSUB

    В regexec() сообщать только об успехе/неудаче сопоставления (и не устанавливать значения поля   re_nsub структуры regex_t ).

    REG_NEWLINE

    Изменить трактовку переводов строк (мы не будем на этом останавливаться).

    Функция regexec() сопоставляет цепочку символов   string со скомпилированным шаблоном, заданным аргументом preg. При успешном выполнении результат равен нулю; в противном случае возвращается ненулевое значение, свидетельствующее о неудаче сопоставления или ошибке. Аргумент eflags - побитное ИЛИ флагов REG_NOTBOL и REG_NOTEOL - определяет, являются ли границы цепочки границами строки, что важно для обработки фиксаторов ^ и $. Если значение аргумента nmatch равно нулю или при вызове regcomp() был задан флаг REG_NOSUB, аргумент pmatch функции regexec() игнорируется. В противном случае он должен указывать на массив не менее чем из nmatch элементов, который будет заполнен смещениями подцепочек, сопоставленных с заключенными в скобки подвыражениями шаблона ( pmatch [0] соответствует всему регулярному выражению, в неиспользуемые элементы помещается -1).

    Структурный тип regmatch_t должен включать по крайней мере следующие поля:

    regoff_t rm_so; 
    /* Смещение в байтах начала подцепочки 
    от начала цепочки */
    
    regoff_t rm_eo; 
    /* Смещение в байтах первого символа за 
    концом подцепочки от начала цепочки */

    Тип regoff_t определяется как целое со знаком, способное вместить любое значение типов off_t и ssize_t.

    Функция regfree() освобождает память, запрошенную вызовом regcomp() с тем же значением аргумента preg, которое после этого нельзя использовать как указатель на скомпилированное регулярное выражение.

    В файле <regex.h> определены константы, возвращаемые функциями семейства regex() в случае ошибки. Например, значение REG_NOMATCH возвращается функцией regexec() при неудаче сопоставления, REG_BADPAT обозначает некорректное регулярное выражение, REG_ESPACE - нехватку памяти и т.д. Функция regerror() отображает эти константы в неспецифицируемые стандартом цепочки печатных символов и помещает их в буфер errbuf. Приложение, вызывая regerror(), должно передать в качестве аргумента errcode последнее ненулевое значение, возвращенное функциями regcomp() или regexec() с заданным значением аргумента preg.

    Приведем пример использования функций семейства regex() (см. листинг 6.35). Обратим внимание на задание флага REG_NOTBOL при повторных обращениях к regexec().

    #include <stdio.h>
    #include <limits.h>
    #include <regex.h>
    
    /* Программа ищет все вхождения заданного шаблона во всех входных строках */
    /* и выводит успешно сопоставленные подцепочки */
    
    #define PATTERN "[A-Za-z][A-Za-z0-9]{0,31}"
    
    int main (void) {
       char line [LINE_MAX];     /* Буфер для входных строк */
       char *pline;              /* Указатель на начало сопоставляемой части строки */
       regex_t cere;             /* Скомпилированное расширенное регулярное выражение */
       regmatch_t pm;            /* Структура для запоминания границ сопоставленной подцепочки */
       int reerrcode;            /* Код ошибки от regcomp или regexec */
       char reerrbuf [LINE_MAX]; /* Буфер для строк с сообщениями об ошибках */
       int i;
    
       if ((reerrcode = regcomp (cere, PATTERN, REG_EXTENDED)) != 0) {
            (void) regerror (reerrcode, cere, reerrbuf, sizeof (reerrbuf));
            fputs (reerrbuf, stderr);
            fputc ('\n', stderr);
            regfree (cere);
            return (reerrcode);
       }
    
       fputs ("Вводите строки, сопоставляемые с шаблоном " PATTERN "\n", stdout);
       while (fgets (line, sizeof (line), stdin) != NULL) {
       /* Произведем первое сопоставление с прочитанной строкой. */
       /* Оно отличается от остальных при наличии в шаблоне фиксатора начала */
       reerrcode = regexec (cere, pline = line, 1, pm, 0);
          while (reerrcode == 0) {
              /* Повторяем, пока сопоставления с остатком строки успешны */
              fputs ("Сопоставленная подцепочка: ", stdout);
              for (pline += pm.rm_so, i = pm.rm_eo - pm.rm_so; i-- > 0; ) {
                  fputc (*pline++, stdout);
              }
              fputc ('\n', stdout);
              reerrcode = regexec (cere, pline, 1, pm, REG_NOTBOL);
           }
        }
    
        regfree (cere);
        return (ferror (stdin) || ferror (stdout));
    }

    Читателю рекомендуется поэкспериментировать с шаблоном PATTERN, пробуя различные варианты, в том числе некорректные.

    Идейно простым, но весьма мощным и полезным средством обработки текстовых файлов является служебная программа преобразования символов tr:

    tr [-c | -C] [-s] цепочка1 цепочка2
    tr -s [-c | -C] цепочка1
    tr -d [-c | -C] цепочка1
    tr -ds [-c | -C] цепочка1 цепочка2

    Она не применяет регулярных выражений как таковых, но в цепочках - аргументах можно употреблять конструкции, аналогичные некоторым односимвольным РВ.

    Утилита tr копирует стандартный ввод на стандартный вывод с заменой либо удалением выбранных символов. При отсутствии опций введенные символы, найденные в цепочке1, заменяются на соответствующие (стоящие на тех же относительных позициях) символы из цепочки2. Опции -c и -C предписывают использовать вместо цепочки1 ее дополнение до множества всех символов; в первом случае дополнение упорядочивается в соответствии с кодировкой, во втором - по алфавиту. По опции -d будут удалены все входные символы, заданные цепочкой1. Опция -s задает сжатие (до одного) последовательностей одинаковых символов, специфицированных последней из цепочек, указанных в командной строке ( сжатие производится после каждой замены и/или удаления).

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

    c1-c2

    Обозначает цепочку символов, лежащих в диапазоне от c1 до c2 включительно.

    [:класс_символов:]

    Обозначает цепочку символов, принадлежащих указанному классу (см. выше описание базовых регулярных выражений ).

    [=класс_эквивалентности=]

    Обозначает цепочку символов, принадлежащих указанному классу эквивалентности при алфавитном сравнении.

    [c*n]

    Обозначает символ c, повторенный n раз. Может использоваться только в цепочке2. Если первая цифра в n есть 0, n рассматривается как восьмеричное число; иначе - как десятичное. Нулевое или отсутствующее n воспринимается как "очень много"; эта возможность полезна при дополнении цепочки2 до длины цепочки1.

    Обратный слэш можно использовать для задания управляющих символов ( '\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v' ). Кроме того, \ обозначает код символа, если за ним идут одна, две или три восьмеричные цифры.

    Следующая команда (см. листинг 6.36) помещает список всех слов из файла f1, по одному на строку, в файл f2 (под словом понимается максимальная последовательность букв).

    tr -cs '[:alpha:]' '[\n*]' < f1 > f2

    Команда, показанная в листинге 6.37, переводит большие буквы в малые, попутно сжимая последовательности одинаковых (без учета регистра) букв.

    tr -s '[:upper:]' '[:lower:]'

    Служебная программа uniq

    uniq [-c | -d | -u] [-f число] [-s число] 
         [входной_файл [выходной_файл]]

    позволяет сократить до одной подряд идущие одинаковые строки (сделать одинаковые строки файла смежными можно с помощью утилиты sort ). Опции предоставляют дополнительный сервис.

    -c

    Перед каждой выходной строкой помещать ее кратность во входном файле.

    -d

    Подавить вывод неповторяющихся строк.

    -f число

    При сравнении строк игнорировать заданное число начальных полей. Поле определяется как максимальная цепочка символов, успешно сопоставляемая с базовым регулярным выражением   [[:blank:]]*[^[:blank:]]* .

    -s число

    При сравнении строк игнорировать заданное число начальных символов. При совместном использовании опций -f и -c игнорируется указанное число символов, идущих после заданного числа полей.

    -u

    Подавить вывод строк, повторявшихся во входном файле.

    В качестве примера употребления утилиты uniq приведем конвейер, позволяющий найти десять самых употребительных заголовочных файлов среди включаемых в стандартные заголовочные файлы, расположенные в каталоге   /usr/include и его подкаталогах (см. листинг 6.38). Результат работы конвейера может выглядеть так, как показано в листинге 6.39.

    find /usr/include -name '*.h' -exec cat 
         {} \; | tr -d '[:blank:]' | \
    grep -E -e '^#include(<.*>|".*")' 
         | sort | uniq -dc | sort -r | head
    977 #include"nsISupports.h"
    315 #include<glib.h>
    201 #include<gdk/gdk.h>
    167 #include<glibmm.h>
    160 #include<features.h>
    154 #include<glib-object.h>
    144 #include"nsCOMPtr.h"
    139 #include<sys/types.h>
    139 #include<glibmm/class.h>
    135 #include"nscore.h"

    Аналоги реляционных операций для текстовых файлов

    Служебная программа cut

    cut -b список [-n] [файл ...]
    cut -c список [файл ...]
    cut -f список [-d разделитель] 
        [-s] [файл ...]

    используется для выборки байт (опция -b ), символов ( -c ) либо ограниченных разделителями полей (опция -f ) с заданными списком номерами из строк исходных файлов, их конкатенации и выдачи на стандартный вывод. Если применить терминологию реляционных баз данных, cut выполняет операцию проекции отношения.

    Список, являющийся аргументом опций -b, -c и -f, задается как последовательность разделенных запятыми или пробелами положительных чисел или диапазонов (с естественными умолчаниями, когда границы опущены). Опция -d определяет разделитель полей (по умолчанию это символ табуляции), -n предписывает не разрывать (многобайтные) символы, опция -s в сочетании с -f подавляет вывод строк, в которых не оказалось символов-разделителей (по умолчанию такие строки копируются на стандартный вывод без изменений).

    Пример. Чтобы выделить из базы данных пользователей входные имена и идентификаторы, можно воспользоваться командой, показанной в листинге 6.40. Начальный фрагмент возможного результата приведен в листинге 6.41.

    cut -d : -f 1,3 /etc/passwd
    root:0
    bin:1
    daemon:2
    adm:3
    lp:4
    sync:5
    . . .

    Служебная программа paste

    paste [-s] [-d список] файл ...

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

    В качестве имени исходного файла может быть задан минус, что означает стандартный ввод. Если минус употреблен многократно, то стандартный ввод читается построчно, циклически по именам - минусам. Любопытно отметить, что стандарт POSIX-2001 предписывает реализациям поддерживать обработку не менее двенадцати исходных файлов.

    Рассмотрим несколько примеров. Для выдачи в четыре столбца имен файлов текущего каталога можно воспользоваться конвейером, показанным в листинге 6.42.

    ls | paste - - - -

    Для попарного слияния последовательных строк файла целесообразно "сыграть" на том, что список разделителей - кольцевой (см. листинг 6.43).

    paste -s -d "\0\n" f.txt

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

    cut -b 1-80 -n f > f1.txt
    cut -b 81- -n f > f2
    . . .
    paste -d '\0' f1.txt f2 > f3

    Еще один оператор реляционной СУБД - join - выдает на стандартный вывод результат соединения двух отношений, задаваемых строками файла1 и файла2:

    join [-a номер_файла | -v номер_файла] 
       [-e цепочка] [-o список] [-t символ]
       [-1 номер_поля] [-2 номер_поля] файл1 файл2

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

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

    Как правило, поля разделяются символами пробела, табуляции или перевода строки. В этом случае несколько последовательных разделителей интерпретируются как один и начальные разделители отбрасываются. Подразумеваемым разделителем при выводе является пробел.

    Допустимы следующие опции.

    -a номер_файла

    В дополнение к обычному выводу выдать строку для каждой непарной строки из файла с указанным номером (1 или 2).

    -e цепочка

    Заменить пустые поля вывода из списка опции -o заданной цепочкой символов.

    -o список

    Составлять выходные строки из полей, заданных в списке. Элемент списка имеет вид номер_файла.номер_поля или

    -t символ

    Использовать символ в качестве разделителя. Каждое вхождение символа в строку значимо. Указанный символ используется как разделитель и при вводе, и при выводе.

    -v номер_файла

    Вместо подразумеваемого вывода выдавать только непарные строки из файла с указанным номером (1 или 2).

    -1 номер_поля

    Производить соединение по полю   файла1 с заданным номером.

    -2 номер_поля

    Производить соединение по полю   файла2 с заданным номером.

    Рассмотрим примеры. Командная строка (см. листинг 6.45) выполняет соединение баз данных пользователей и групп, отсортированных в порядке возрастания идентификаторов групп. На стандартный вывод поступают входные имена, имена групп и основные каталоги (см. листинг 6.46).

    join -1 4 -2 3 -o 1.1,2.1,1.6 -t : 
         passwd.sorted group.sorted
    halt:root:/sbin
    operator:root:/root
    root:root:/root
    shutdown:root:/sbin
    sync:root:/sbin
    bin:bin:/bin
    daemon:daemon:/sbin
    . . .

    Пусть имеется два упорядоченных по алфавиту справочника: номера телефонов и адреса электронной почты (см. листинг 6.47). Предполагается, что в качестве разделителя полей применяется символ табуляции. Тогда командная строка, показанная в листинге 6.48, позволит получить объединенный справочник (см. листинг 6.49).

    Имя Номер телефона
    Иван 123-4567
    Петр 123-5678
    Яков 123-6789
    
    Имя Адрес электронной почты
    Иван ivan123@mail.ru
    Олег oleg@yahoo.com
    Яков yak@yandex.ru
    join -t '<tab>' -a 1 -a 2 -e '---------' 
         -o 0,1.2,2.2 phone.txt email.txt
    Имя Номер телефона Адрес электронной почты
    Иван 123-45-67 ivan123@mail.ru
    Олег --------- oleg@yahoo.com
    Петр 123-56-78 ---------
    Яков 123-67-89 yak@yandex.ru

    Читателю предлагается самостоятельно выбрать правильный способ задания символа табуляции в качестве аргумента опции -t служебной программы join, а также объяснить, зачем нужен пробел в начале заголовка каждого из справочников.

    Средства обработки каталогов

    Обработка каталогов, как и обычных файлов, начинается с их открытия. Для этого предназначена функция opendir() (см. листинг 6.50).

    #include <dirent.h>
    DIR *opendir (const char *dirname);

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

    #include <dirent.h>
    void rewinddir (DIR *dirp);

    Чтение элементов каталога выполняет функция readdir() (см. листинг 6.52), которая возвращает указатель на структуру, представляющую текущий элемент каталога ; после ее завершения текущим становится следующий элемент достижении конца каталога и в случае ошибки возвращается пустой указатель. Следовательно, если приложению необходимо различать обе ситуации, оно должно обнулить значение переменной   errno перед вызовом readdir(), а затем, если результат равен NULL, проанализировать это значение.

    #include <dirent.h>
    struct dirent *readdir (DIR *dirp);

    Согласно стандарту POSIX-2001, структура dirent содержит по крайней мере одно поле:

    char d_name []; /* Имя файла */

    В качестве необязательного описано еще одно поле:

    ino_t d_ino; /* Порядковый номер файла */

    Если элемент каталога представляет символьную ссылку, значение этого поля не определено.

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

    После завершения работы с каталогом его следует закрыть с помощью функции closedir() (см. листинг 6.53), возвращающей

    #include <dirent.h>
    int closedir (DIR *dirp);

    Нередко чтение элементов каталога и сопоставление с шаблоном имен файлов сочетаются (см. выше раздел "Генерация маршрутных имен файлов" ). Подобное сопоставление реализует функция fnmatch() (см. листинг 6.54).

    #include <fnmatch.h>
    int fnmatch (const char *file_pattern, 
       const char *file_name, int flags);

    На процесс сопоставления имени file_name с шаблоном   file_pattern влияют следующие флаги:

    FNM_PATHNAME

    Трактовать имя file_name как маршрутное. Символу / в имени должен явным образом сопоставляться этот же символ в шаблоне (а не ? или выражение в квадратных скобках). Если флаг не установлен, символ / трактуется наравне с другими.

    FNM_NOESCAPE

    При наличии этого флага символ \ трактуется наравне с другими. В противном случае он играет экранирующую роль.

    FNM_PERIOD

    Если этот флаг установлен, точка в начале имени файла должна сопоставляться с точкой в шаблоне. Иначе точка в начале имени сопоставляется на общих основаниях.

    В случае успешного сопоставления функция fnmatch() возвращает ноль, при неудаче результат равен FNM_NOMATCH, в случае ошибки возвращается другое ненулевое значение.

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

    #include <dirent.h>
    #include <fnmatch.h>
    #include <errno.h>
    #include <stdio.h>
    
    /* Программа сопоставляет имена файлов текущего каталога с заданными шаблонами */
    
    #define SEARCH_DIR "."
    
    static void match_names (DIR *dirp, const char *pattern) {
       struct dirent *dp;
    
       rewinddir (dirp);
    
       while (errno = 0, (dp = readdir (dirp)) != NULL) {
          if (fnmatch (pattern, dp->d_name, FNM_PERIOD) == 0) {
            (void) printf (" %s\n", dp->d_name);
          }
       }
    
       if (errno != 0) {
           perror ("Ошибка при чтении каталога " SEARCH_DIR);
       }
    }
    
    int main (int argc, char *argv []) {
       DIR *dirp;
       int i;
    
       if ((dirp = opendir (SEARCH_DIR)) == NULL) {
          perror ("Ошибка при открытии каталога " SEARCH_DIR);
          return (-1);
       }
    
       for (i = 1; i < argc; i++) {
         (void) printf ("Файлы каталога " SEARCH_DIR ", удовлетворяющие шаблону %s\n", argv [i]);
         match_names (dirp, argv [i]);
       }
    
       return (closedir (dirp));
    }

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

    #include <glob.h>
    int glob (const char *restrict file_pattern, 
      int flags, int (*errfunc) (const char 
      *epath, int eerrno), glob_t *restrict pglob);
    void globfree (glob_t *pglob);

    Функция glob() сопоставляет все доступные маршрутные имена с шаблоном   file_pattern и генерирует список успешно сопоставленных имен. Результаты своей работы функция glob() возвращает в структуре типа glob_t, содержащей, согласно стандарту, по крайней мере следующие поля:

    size_t gl_pathc; 
    /* Число успешно сопоставленных маршрутных 
    имен */
    
    char **gl_pathv; 
    /* Массив указателей на успешно сопоставленные 
    маршрутные имена */
    
    size_t gl_offs; 
    /* Число элементов, которые нужно зарезерви-
    ровать в начале массива gl_pathv */

    Структура, на которую указывает аргумент pglob, должна создаваться приложением. Другие области памяти, в том числе массив gl_pathv, резервируются функцией glob() по мере необходимости и освобождаются после обращения к globfree() с тем же значением pglob.

    Работой функции glob() можно управлять с помощью следующих флагов.

    GLOB_APPEND

    Добавлять вновь сгенерированные маршрутные имена к предыдущим результатам функции glob().

    GLOB_DOOFFS

    Принимать во внимание поле   gl_offs. Если этот флаг установлен, то pglob->gl_pathv будет указывать на pglob->gl_offs пустых указателей, за которыми следуют pglob->gl_pathc указателей на маршрутные имена и завершающий пустой указатель.

    GLOB_ERR

    Завершать работу по достижении каталога, который не удается открыть или прочитать. Обычно в таких случаях функция glob() продолжает процесс генерации маршрутных имен.

    GLOB_MARK

    Добавлять символ / после имен успешно сопоставленных файлов, являющихся каталогами.

    GLOB_NOCHECK

    При отсутствии успешно сопоставленных имен возвращать шаблон.

    GLOB_NOESCAPE

    Запретить экранирование символом \.

    GLOB_NOSORT

    Не сортировать сгенерированные имена по алфавиту.

    Если в процессе своей работы функция glob() достигает каталога, который не удается открыть или прочитать, а аргумент errfunc отличен от пустого указателя, то glob() вызывает (*errfunc ()) с двумя аргументами: "проблемным" маршрутным именем и соответствующим значением errno. Если этот вызов возвращает значение, отличное от нуля, или если установлен флаг GLOB_ERR, генерация маршрутных имен завершается, значения полей   pglob->gl_pathc и pglob->gl_pathv устанавливаются в соответствии с уже обработанными маршрутами, а результат вызова функции glob() полагается равным GLOB_ABORTED (при нормальном завершении возвращается 0).

    Перепишем программу, которая выводит имена файлов, удовлетворяющие заданным в командной строке шаблонам, воспользовавшись функцией glob() (см. листинг 6.57).

    #include <glob.h>
    #include <errno.h>
    #include <stdio.h>
    
    /* Программа выводит маршрутные имена, сгенерированные по заданным шаблонам */
    
    static int errfunc (const char *epath, int eerrno) {
        fprintf (stderr, "Ошибка при обработке каталога %s: ", epath);
        errno = eerrno;
        perror (NULL);
        return (0);
    }
    
    int main (int argc, char *argv []) {
        glob_t gl_buf;
        int i;
    
        for (i = 1; i < argc; i++) {
            (void) glob (argv [i], ((i == 1) ?
        }
    
        (void) printf ("Маршрутные имена, сгенерированные по заданным шаблонам:\n");
        for (i = 0; (unsigned) i < gl_buf.gl_pathc; i++) {
            (void) printf ("%s\n", gl_buf.gl_pathv [i]);
        }
    
        globfree (gl_buf);
        return (0);
    }

    Выше, при описании языка shell, мы рассматривали служебную программу basename. Ее логическим дополнением является утилита dirname:

    dirname цепочка_символов

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

    В качестве примера использования утилиты dirname приведем фрагмент командного файла, выполняемого при загрузке ОС Linux (см. листинг 6.58).

    if [ "`dirname $RAW`" = "/dev/raw" -a -f /dev/raw ]; then
       echo $" Please correct your /etc/sysconfig/rawdevices:"
       echo $" rawdevices are now located in the directory /dev/raw/ "
       echo $" If the command 'raw' still refers to /dev/raw as a file."
       echo $" you'll have to upgrade your util-linux package"
       exit
    fi

    Совместное использование утилит basename и dirname иллюстрирует листинг 6.59. Это командный файл с одним аргументом - маршрутным именем компилируемого C-файла, которое может быть задано как с расширением .c, так и без него.

    gcc -Wall -W -pedantic -o $(basename "$1" .c) 
         $(dirname "$1")/$(basename "$1" .c).c
    Страницы:

    Простейшие средства обработки текстовых файлов

    Согласно определению, данному в стандарте POSIX-2001, текстовым называется файл, символы которого объединены в строки длиной не более {LINE_MAX}, не содержащие нулевого символа.

    Вероятно, простейшей операцией с файлами (не обязательно текстовыми) является их выдача на стандартный вывод, что обеспечивается посредством утилиты

    cat [-u] [файл ...]

    Несмотря на внешнюю простоту, применение служебной программы cat сопряжено с рядом тонкостей. Во-первых, опция -u предписывает передавать на стандартный вывод байты из входных файлов без задержек, это означает, в частности, отмену буферизации стандартного вывода. Во-вторых, если не указаны исходные файлы или в качестве аргумента задан знак минус, используется стандартный ввод. Минус может фигурировать в командной строке несколько раз, стандартный ввод не закрывается и не переоткрывается. Например, допустимы команды, показанные в листинге 6.1, причем первая обеспечит ввод двух интерактивных вставок, а вторая эквивалентна третьей (читателю предлагается самостоятельно объяснить данный факт).

    cat f1.txt - f2.txt - f3.txt
    cat g1.txt - g2.txt - g3.txt < input.txt
    cat g1.txt - g2.txt /dev/null g3.txt < input.txt
    cat f1.txt f2.txt > f1.txt

    В-третьих, нужно учитывать особенности перенаправления вывода командным интерпретатором shell. В общем случае утилита cat выдает результат конкатенации содержимого файлов -аргументов, но выполнение четвертой команды, показанной в листинге 6.1, приведет к утрате содержимого файла f1.txt.

    Для просмотра больших текстовых файлов предпочтительнее пользоваться служебной программой

    more [опция ...] [файл ...]

    Она разбивает вывод на страницы (экраны) и выдает их по явному указанию пользователя (например, по нажатию клавиши пробела).

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

    -n число

    Задает число строк, выдаваемых на экран.

    -p команды more

    Команды more выполняются после входа в очередной файл и вывода его первого экрана. Это может быть, к примеру, команда поиска.

    -s

    Сжимать последовательные пустые строки в одну.

    Утилита more часто используется как заключительное звено конвейеров для удобного просмотра результатов.

    Для просмотра нетекстовых файлов рекомендуется служебная программа

    od [опция ...] [файл ...]

    Она выдает на стандартный вывод содержимое исходных файлов в формате, заданном пользователем, а также в виде блоков, каждый из которых в простейшем (и наиболее употребительном) случае состоит из шестнадцати байт и занимает при выводе одну строку. В начале подобной строки располагается смещение блока от начала файла, затем следует содержимое блока. С помощью аргумента опции -A можно задать основание системы счисления для смещений ( d - десятичное, o - восьмеричное, x - шестнадцатеричное, n - не выдавать смещение). Формат выдачи содержимого определяется аргументом опции -t. Помимо перечисленных типов могут быть заданы a ( именованные символы ), c (символы), f (вещественные числа), u (беззнаковые десятичные). За спецификациями d, f, o, u и x может следовать десятичное число - количество байт в одном значении заданного типа; за спецификацией f - символы F, D или L, указывающие тип вещественных чисел ( float, double или long double, соответственно), а за спецификациями d, o, u, x - спецификаторы целочисленного типа C ( char ), S ( short ), I ( int ) или L ( long ).

    Рассмотрим пример. Пусть файл ascii.tab содержит байты от

    od -A x -t a ascii.tab
    000000 nul soh stx etx eot enq ack bel  bs  ht  nl  vt  ff  cr  so  si
    000010 dle dc1 dc2 dc3 dc4 nak syn etb can  em sub esc  fs  gs  rs  us
    000020  sp   !   "   #   $   %      '   (   )   *   +   ,   -   .   /
    000030   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
    000040   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
    000050   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
    000060   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
    000070   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~ del
    000080

    Полезная возможность - взглянуть на начало файла, чтобы понять, нуждается ли он в более детальном изучении. Эта возможность реализуется служебной программой head:

    head [-n число] [файл ...]

    Утилита head копирует на стандартный вывод указанное число (по умолчанию - 10) начальных строк исходных файлов (или весь файл, если он слишком короткий).

    "Симметричная" служебная программа - tail

    tail [-f] [-c число | -n число] [файл]

    выдает на стандартный вывод конец исходного файла, начиная с указанного места. Если задана опция -c, единицей измерения является байт, если -n - строка. Числовой аргумент опции может быть задан со знаком; знак + означает, что отсчет байт (строк) идет от начала файла.

    Опция -f полезна для слежения за растущими файлами, в которые пишет какой-либо другой процесс. При наличии этой опции утилита tail не завершается по достижении конца файла, но ждет появления новых данных и выводит их.

    Рассмотрим примеры. Команда

    tail -f f1

    выдаст последние десять строк файла f1, за которыми последуют строки, добавленные к файлу f1 в промежуток времени между запуском и терминированием утилиты tail.

    В качестве примера совместного использования служебных программ head и tail приведем фрагмент одного из командных файлов ОС Linux (см. листинг 6.4).

    echo "Reloading automounter: checking\
    for changes ... "
    TMP=/var/run/autofs.tmp
    getmounts > $TMP
    for i in /var/run/autofs.*.pid
    do
     pid=`head -n 1 $i 2>/dev/null`
     [ "$pid" = "" ]  continue
     command=`tail -n +2 $i`
     if ! grep -q "^$command" $TMP
     then
       echo "Stopping automounter: $command"
       kill -USR2 $pid
     fi
    done
    rm -f $TMP

    Отметим, что здесь утилиты head и tail делят исходный файл "по-братски": первой достается первая строка, второй - все остальные.

    Служебная программа pr

    pr [+номер_страницы] [-число_столбцов] 
    [-adFmrt] [-e[символ][число]]
    [-h заголовок] [-i[символ][число]] 
    [-l число_строк] [-n[символ][ширина]]
    [-o отступ] [-s[символ]] [-w ширина] 
    [файл ...]

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

    Утилита pr обрабатывает следующие аргументы командной строки.

    +номер_страницы

    Начать вывод со страницы с заданным номером (по умолчанию с первой).

    -число_столбцов

    Вывод в заданное число столбцов (по умолчанию в один). При выводе в несколько столбцов автоматически действуют опции -e и -i. Этот аргумент несовместим с опцией -m.

    -a

    Выдача в несколько столбцов с упорядочением по строкам. Например, если заказана печать в два столбца, первая исходная строка попадает в первый столбец, вторая - во второй, третья - снова в первый и т.п.

    -d

    Выдача через строку.

    -e[символ][число]

    Установка табуляции через заданное число позиций, начиная с первой. Символы табуляции во входном файле развертываются в соответствующее количество пробелов. Если задан любой нецифровой символ, он трактуется как символ табуляции во входном файле.

    -F

    Применять символы перехода к новой странице (по умолчанию переход на новую страницу осуществляется при помощи последовательности переводов строк).

    -h заголовок

    Использовать заданный заголовок (по умолчанию в качестве заголовка выступает имя исходного файла).

    -i[символ][число]

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

    -l число_строк

    Установка длины страницы (по умолчанию 66 строк).

    -m

    Слияние и печать всех файлов параллельно, по одному в столбце. Реализация должна поддерживать слияние по крайней мере девяти файлов.

    -n[символ][ширина]

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

    -o отступ

    Установка отступа слева на заданное число позиций. Количество позиций в каждой строке есть сумма ширины и отступа.

    -p

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

    -r

    Подавление диагностических сообщений при ошибках открытия файлов.

    -s[символ]

    Разделение столбцов единственным символом (по умолчанию - табуляцией), а не соответствующим количеством пробелов.

    -t

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

    -w ширина

    Установка ширины строки (по умолчанию - 72 позиции). Эта опция действует только при печати в несколько столбцов.

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

    ls -a | pr -2 -a -l 24 -n' '3 -h "Список\
    файлов каталога $(pwd)"

    Начальный фрагмент результата может выглядеть так, как показано в листинге 6.5.

    2003-10-17 17:24 Список файлов каталога
    /var/tmp Page 1
    1 . 2 ..
    3 Blank.ReS 4 Make45.ReS
    5 Make46.ReS 6 from_cvs
    7 gcc-20032204 8 gcc-3.4-16-jun-2003
    9 htdocs 10 rpm-tmp.29785
    . . .

    Для подсчета числа символов, слов и строк в файлах служит утилита wc:

    wc [-c | -m] [-w] [-l] файл ...

    Опция -c предписывает подсчитывать байты, -m - символы, -w - слова, -l - строки. По умолчанию подсчитываются символы, слова и строки. Если в командной строке задано несколько файлов, помимо результатов для каждого из них выводятся также суммарные показатели.

    Рассмотрим пример. Предположим, что результаты, показанные в листинге 6.3, сохранены в файле ascii.od. Тогда команда, приведенная в листинге 6.6, выдаст результат, показанный в листинге 6.7.

    wc ascii.od
    9 137 575 ascii.od

    Иными словами, в указанном файле 575 символов, 137 слов, расположенных на 9 строках.

    Служебная программа sort

    sort [-m] [-o выходной_файл] [-bdfinru] 
    [-t символ] [-k определение_ключа] ... 
    [файл ...]
    
    sort -c [-bdfinru] [-t символ] 
    [-k определение_ключа] [файл]

    в зависимости от заданных опций выполняет одно из трех возможных действий:

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

    Следующие опции управляют порядком работы утилиты sort.

    -c

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

    -m

    Слияние исходных файлов, которые предполагаются отсортированными.

    -o выходной_файл

    Результат направляется не на стандартный вывод, а в выходной_файл, который может совпадать с одним из исходных.

    -u

    Опция уникальности: из всех совпадающих строк выводить только одну, при наличии опции -c контролировать отсутствие строк с совпадающими ключами сортировки.

    Следующие опции изменяют подразумеваемый способ сравнения. Если они употреблены независимо от определения ключей сортировки, то диктуемые ими правила действуют глобально, на все ключи.

    -d

    "Словарный" порядок: при сравнении являются значимыми только буквы, цифры и пробельные символы.

    -f

    При сравнении преобразовывать малые буквы в большие.

    -i

    При сравнении игнорировать непечатные символы.

    -n

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

    -r

    Заменить результат сравнения на противоположный.

    Следующие опции изменяют трактовку разделителей полей.

    -b

    Игнорировать начальные пробельные символы при определении начала и конца ключей сортировки. Если опция -b указана перед первой опцией -k, она воздействует на все ключи, снабженные опцией -k.

    -t символ

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

    Ключи сортировки определяются с помощью опции -k. Определение ключа имеет следующий формат:

    начало_поля[тип][,конец_поля[тип]]

    Ключевое поле охватывает все позиции - от начальной до конечной, которые задаются, соответственно, как

    номер_поля[.первый_символ]
    номер_поля[.последний_символ]

    Тип - это один из модификаторов b, d, f, i, n, r. Модификатор b ведет себя аналогично опции -b, но применяется только к той границе ключа, после которой он указан. Остальные модификаторы также аналогичны одноименным опциям, но воздействуют на ключ в целом.

    Если указано несколько ключей сортировки (согласно стандарту, реализацией должно поддерживаться по крайней мере девять вхождений опции -k ), то более поздние используются только в случае равенства более ранних. Если значения ключей сортировки двух строк совпадают, строки упорядочиваются с учетом всех символов.

    Приведем примеры использования служебной программы sort. Автор хранит данные о собственном кровяном давлении в виде файла pp.txt, содержащего пять столбцов: дата, время, верхнее давление, нижнее давление, пульс (см. листинг 6.8). Естественно, строки добавляются к файлу в хронологическом порядке. Чтобы упорядочить их по убыванию верхнего давления (третий столбец ), можно воспользоваться командой, показанной в листинге 6.9. Возможный результат приведен в листинге 6.10.

    12.05.2000 17:30 200 125 120
    15.05.2000 17:00 130 80 70
    17.05.2000 10:30 150 90 70
    17.05.2000 21:45 154 99 74
    19.05.2000 10:05 158 83 89
    21.05.2000 21:00 161 104 64
    22.05.2000 21:00 147 104 69
    . . .
    sort -r -k 3,3 pp.txt
    12.05.2000 17:30 200 125 120
    18.11.2000 19:30 172 107 68
    04.07.2002 09:00 170 98 85
    30.10.2001 13:00 168 94 88
    27.12.2000 20:30 166 98 69
    23.05.2002 10:00 166 104 56
    22.05.2002 10:00 166 103 57
    23.10.2001 11:00 165 88 88
    . . .

    Если ключи сортировки равны, строки упорядочиваются с учетом всех байт. В рассматриваемом примере это не очень удобно, поскольку они начинаются с номера дня в месяце. Более естественно либо расширить ключ сортировки путем охвата полей нижнего давления и пульса, либо сделать эти поля дополнительными ключами. Оба варианта показаны в листинге 6.11, а результат сортировки - в листинге 6.12. Отметим, что второй вариант предпочтительнее, поскольку он устойчив к ошибкам выравнивания столбцов.

    # Первый вариант - ключ сортировки покрывает
    # несколько полей sort -r -k 3,5 pp.txt
    # Второй вариант - используется несколько 
    # ключей сортировки 
    # sort -n -r -k 3,3 -k 4,4 -k 5,5 pp.txt
    12.05.2000 17:30 200 125 120
    18.11.2000 19:30 172 107 68
    04.07.2002 09:00 170 98 85
    30.10.2001 13:00 168 94 88
    23.05.2002 10:00 166 104 56
    22.05.2002 10:00 166 103 57
    27.12.2000 20:30 166 98 69
    23.10.2001 11:00 165 88 88
    . . .

    Предположим теперь, что данные о давлении и пульсе разбиты по годам и в уже отсортированном виде хранятся в файлах pp2000.sorted, pp2001.sorted и т.д. Для их слияния можно воспользоваться командной строкой или циклом из листинга 6.13. Опять-таки второй вариант предпочтительнее по причине его устойчивости к количеству исходных файлов.

    # Первый вариант слияния файлов 
    # sort -m -o pp.sorted -n -r -k 3,3 -k 4,4
    # -k 5,5 pp2*.sorted
    # Второй вариант слияния файлов
    > pp.sorted
    for f in pp2*.sorted
       do
          sort -m -o pp.sorted -n -r -k 3,3\
          -k 4,4 -k 5,5 $f pp.sorted
       done

    Проверить правильность слияния можно с помощью команд, показанных в листинге 6.14.

    name=pp.sorted
    if sort -c -r -n -k 3,3 -k 4,4 -k 5,5 $name
    then
       echo Данные в файле $name отсортированы\
        верно
    elif
       echo Данные в файле $name отсортированы\
        неверно
    fi

    Содержимое файлов f1 и f2 сортирует команда (см. листинг 6.15) , применяющая в качестве ключа второй непробельный символ второго поля.

    sort -k 2.2b,2.2b f1 f2

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

    sort -t ':' -k 3,3n /etc/passwd

    Выдать содержимое уже отсортированного файла   f.sorted, выводя лишь первую из строк с одинаковыми третьими полями, поможет комбинация опций -m и -u (см. листинг 6.17).

    sort -um -k 3.1,3 f.sorted

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

    diff [-c | -e | -f | -C число] [-br] 
         файл1 файл2

    Утилита diff сравнивает содержимое исходных файлов и выдает на стандартный вывод список изменений, которые необходимо произвести, чтобы преобразовать файл1 в файл2. Она поддерживает следующие опции.

    -b

    Игнорировать пробельные символы в конце строк; остальные цепочки пробельных символов считать равными.

    -c

    Производить вывод в формате, обеспечивающем три строки контекста.

    -C число

    Производить вывод в формате, обеспечивающем заданное число строк контекста.

    -e

    Производить вывод в формате, пригодном для подачи на вход редактора ed и преобразования файла1 в файл2.

    -f

    Производить вывод в альтернативном формате, напоминающем -e, но в обратном порядке и не предназначенном для подачи на вход редактора ed.

    -r

    Если файл1 и файл2 представляют собой каталоги, применять утилиту diff рекурсивно к одноименным и однотипным обычным файлам и каталогам. Если только один из файлов является каталогом, в нем сравнивается "тезка" другого исходного файла.

    В качестве примера использования служебной программы diff сравним две версии бинарных утилит (см. листинг 6.18). Начальный фрагмент результата показан в листинге 6.19. Видно, что различия, по сути, сводятся к добавлению новых файлов.

    diff -r binutils-2_14 binutils-2_14-branch
    diff -r binutils-2_14/bfd/version.h binutils-2_14-branch/bfd/version.h
    1c1
    < #define BFD_VERSION_DATE 20030612
    ---
    > #define BFD_VERSION_DATE 20031007
    Only in binutils-2_14-branch/binutils: ChangeLog
    Only in binutils-2_14-branch/binutils: arlex.c
    Only in binutils-2_14-branch/binutils: deflex.c
    Only in binutils-2_14-branch/binutils: rclex.c
    Only in binutils-2_14-branch/binutils: syslex.c
    Only in binutils-2_14-branch: config.guess
    Only in binutils-2_14-branch/gas: ChangeLog
    Only in binutils-2_14-branch/gas/config: tc-ns32k.c
    Only in binutils-2_14-branch/gas: configure
    Only in binutils-2_14-branch/gas: configure.in
    Only in binutils-2_14-branch/gas: itbl-lex.c
    . . .

    Если нужно проверить два файла на совпадение, предпочтительнее воспользоваться не утилитой diff, а более простой и быстрой служебной программой cmp:

    cmp [-l | -s] файл1 файл2

    По умолчанию cmp выдает на стандартный вывод номера байта и строки, где встретилось первое отличие. Опция -l предписывает выдавать все отличия в виде номеров и содержимого (восьмеричного) байт. По опции -s отличия не выдаются, возвращается лишь соответствующий код завершения (0 - файлы совпадают, 1 - файлы различаются, больше единицы - зафиксирована ошибка).

    Если в продолжение примера с утилитой diff сравнить две версии файла version.h посредством cmp (см. листинг 6.20), результат будет выглядеть так, как показано в листинге 6.21.

    cmp -l binutils-2_14/bfd/version.h\
      binutils-2_14-branch/bfd/version.h
    30 60 61
    31 66 60
    32 61 60
    33 62 67

    Еще одно средство выявления различий (и, кроме того, совпадений) текстовых файлов - служебная программа comm:

    comm [-123] файл1 файл2

    Она читает файл1 и файл2 (их содержимое должно быть отсортировано в лексикографическом порядке) и выводит результаты в три столбца:

  • строки, входящие только в файл1;
  • строки, входящие только в файл2;
  • строки, входящие в оба файла.
  • Опции -1, -2 или -3 подавляют вывод соответствующих столбцов. Так, comm -12 выводит строки, общие для обоих файлов, comm -23 - имеющиеся только в файле1, comm -123 не выводит ничего.

    В стандарте POSIX-2001 приведен следующий пример использования служебной программы comm. Пусть файл xcu содержит отсортированный список утилит, вошедших в данный стандарт, файл xpg3 - аналогичный список для спецификации X/Open Portability Guide, Issue 3, а файл svid89 представляет собой отсортированный список утилит из System V Interface Definition Third Edition. Тогда конвейер, показанный в листинге 6.22, выдаст список служебных программ, фигурирующих в спецификациях XPG3 и SVID, но не вошедших в стандарт IEEE Std1003.1-2001.

    comm -12 xpg3 svid89 | comm -23 - xcu

    Для контроля целостности файлов (не являющегося, впрочем, криптостойким), предназначена служебная программа

    cksum [файл ...]

    Для каждого файла - аргумента она выдает на стандартный вывод подсчитанную по стандарту ISO/IEC 8802-3:1996 (Ethernet) контрольную сумму, количество байт и имя.

    Применение утилиты cksum - полезный элемент оформления поставляемых приложений.

    Регулярные выражения

    Понятие регулярного выражения (РВ) - одно из важнейших для программ обработки текстовых файлов. Согласно стандарту POSIX-2001, регулярное выражение - это шаблон, служащий для выборки определенных цепочек символов из множества подобных цепочек. Говорят, что выбранные цепочки удовлетворяют РВ (успешно с ним сопоставляются).

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

    Стандарт накладывает на сложность (полных) РВ единственное ограничение: реализация должна поддерживать любое РВ, длина которого не превышает 256 байт.

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

    Различают базовые (БРВ) и расширенные регулярные выражения (РРВ). В большинстве случаев используются БРВ, они и будут описаны в первую очередь.

    Минимальным элементом БРВ являются односимвольные БРВ, т. е. БРВ, которым удовлетворяют цепочки из одного символа. Односимвольные БРВ строятся по следующим правилам.

  • Обычный символ (не входящий в перечень из следующего пункта) - это односимвольное   БРВ, которое успешно сопоставляется с указанным символом.
  • Если за символом \ следует любой специальный символ, то последний теряет свой специальный смысл. Данная комбинация определяет односимвольное   БРВ, успешно сопоставляющееся со специальным символом. Специальными символами являются:
    . [ \
    Эти символы имеют специальный смысл, за исключением случая, когда они заключены в квадратные скобки.
    *
    Символ имеет специальный смысл, если он не заключен в квадратные скобки или не является первым символом БРВ.
    ^
    Символ имеет специальный смысл в начале полного БРВ или в квадратных скобках сразу после открывающей скобки.
    $
    Символ имеет специальный смысл в конце полного БРВ.
  • Точка ( .) - односимвольное   БРВ, успешно сопоставляющееся с любым символом.
  • Непустая цепочка символов, заключенная в квадратные скобки [ ] представляет собой односимвольное   БРВ, которое успешно сопоставляется с любым символом из этой цепочки. Если цепочка начинается с символа ^, то БРВ успешно сопоставляется с любым не входящим в данный набор символом.
  • Внутри квадратных скобок можно использовать также конструкции, описываемые ниже.

    Диапазон символов можно задать с помощью знака -. Например, БРВ   [0-9] эквивалентно [0123456789]. Минус теряет свой специальный смысл, если он стоит в начале (допустимо после ^ ) или в конце цепочки символов в квадратных скобках либо использован в качестве границы диапазона. Так, БРВ   [%--] успешно сопоставляется со всеми символами от процента до минуса включительно.

    Закрывающая квадратная скобка не рассматривается как окончание цепочки символов, если она стоит вначале (быть может, после ^ ). Таким образом, БРВ   []a-f] определяет либо закрывающую квадратную скобку, либо любой символ от a до f включительно.

    Стандарт POSIX-2001 предусматривает поддержку классов символов (таких, например, как буквы, цифры, знаки пунктуации, пробельные символы, управляющие символы и т.д.). Класс символов задается своим именем, заключенным в квадратные скобки с двоеточиями. Должны поддерживаться по крайней мере следующие классы:

    [:alnum:] [:cntrl:] [:lower:] [:space:]
    [:alpha:] [:digit:] [:print:] [:upper:]
    [:blank:] [:graph:] [:punct:] [:xdigit:]

    Например, БРВ [^[:alnum:]] удовлетворяют символы, отличные от букв и цифр.

    В некоторых языках элемент алфавитного сравнения может задаваться несколькими символами. Такие элементы следует заключать в квадратные скобки с точками: [[.ch.]].

    Если определены классы элементов, эквивалентных при алфавитном сравнении, то подобный класс можно задать, заключив любой его элемент в квадратные скобки со знаками равенства: ([= и =]).

    Таковы правила построения заключенных в квадратные скобки односимвольных БРВ.

    Построение многосимвольных   БРВ из односимвольных регламентируется следующим образом.

  • В состав БРВ могут входить подвыражения ( в том числе вложенные), заключенные в экранированные круглые скобки: \( и \). Подвыражение в скобках успешно сопоставляется с теми же цепочками, что и без скобок.
  • На подвыражения допускаются обратные ссылки вида \n (где n - цифра от 1 до 9). Обратной ссылке удовлетворяет такая же цепочка символов, что была успешно сопоставлена подвыражением, открывающимся экранированной скобкой номер n, считая от начала полного БРВ. Например, БРВ \(.\)\1 удовлетворяют пары одинаковых символов.
  • За односимвольными БРВ, подвыражениями и обратными ссылками может следовать звездочка. Такая конструкция успешно сопоставляется с любым (в частности, с нулевым) числом последовательных вхождений упомянутых БРВ. Например, шаблону \(.*\)\1 удовлетворяют пары стоящих рядом одинаковых цепочек символов.
  • За односимвольными   БРВ, подвыражениями и обратными ссылками может следовать запись вида \{m\}, \{m,\} или \{m,n\} , называемая интервальным выражением. Такая конструкция успешно сопоставляется с определенным числом вхождений упомянутых БРВ. Значения m и n должны удовлетворять неравенствам
  • Конкатенация компонентных БРВ есть БРВ, которое успешно сопоставляется с конкатенацией цепочек, удовлетворяющих каждому из компонентных БРВ.
  • На БРВ можно наложить ограничение, чтобы успешно сопоставленная   подцепочка символов примыкала к одной или обоим границам анализируемой цепочки (произвести фиксацию границ ). Если в начале БРВ стоит символ ^ ( фиксатор начала ), то сопоставление должно выполняться с начала цепочки .   Если в конце БРВ стоит символ $ ( фиксатор конца ), то сопоставление должно выполняться до конца цепочки . Например, БРВ ^\(.*\)\1$ успешно сопоставляется с цепочками символов, состоящими из двух одинаковых подцепочек.
  • Операции построения многосимвольных   БРВ описаны выше в порядке убывания приоритетов. Наибольшим приоритетом обладают подвыражения и обратные ссылки, далее следуют повторители - звездочка и интервальные выражения, затем конкатенация и, наконец, фиксация границ.

    Опишем отличия расширенных регулярных выражений (РРВ) от базовых.

  • В число специальных символов дополнительно входят круглые скобки, открывающая фигурная скобка, а также символы ?, |.
  • Для группирования (выделения подвыражений ) используются неэкранированные круглые скобки.
  • Отсутствуют обратные ссылки.
  • Добавлены два повторителя: плюс обозначает любое ненулевое число последовательных вхождений; знак вопроса - нуль или одно вхождение.
  • Для выделения интервальных выражений используются неэкранированные фигурные скобки.
  • Введена операция логического ИЛИ, которая обозначается символом | и имеет низший приоритет. Результат сопоставляется с цепочками, удовлетворяющими хотя бы одному из операндов. Например, РРВ   a((bc)|d) успешно сопоставляется и с цепочкой символов   "abc", и с "ad". Односимвольные РРВ, разделенные символом | и заключенные в круглые скобки, трактуются как односимвольные.
  • Таковы правила построения и обработки регулярных выражений, зафиксированные в стандарте POSIX-2001. Отметим, что базовые регулярные выражения не являются подмножеством расширенных, хотя число специфических особенностей БРВ невелико.

    Использование регулярных выражений

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

    grep [-E | -F] [-c | -l | -q] [-insvx] 
         -e список_шаблонов ... 
    	 [-f файл_шаблонов] ... [файл ...]
    
    grep [-E | -F] [-c | -l | -q] [-insvx]
         [-e список_шаблонов ...] 
         -f файл_шаблонов ... [файл ...]
    
    grep [-E | -F] [-c | -l | -q] [-insvx] 
         список_шаблонов [файл ...]

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

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

    По умолчанию шаблоны трактуются как базовые регулярные выражения. Опция -E предписывает переход к расширенным РВ, а опция -F означает, что в качестве шаблонов выступают цепочки символов (и, следовательно, процесс сопоставления существенно упрощается).

    Опции -c, -l, -q и -n влияют на выдачу результатов. По умолчанию на стандартный вывод выдаются строки исходных файлов, в которых присутствуют успешно сопоставленные   цепочки символов. Опция -c предписывает выдавать только общее число подобных строк, -l - только имена файлов, где имеются успешно сопоставленные строки, -q - только код завершения (0 - есть успешно сопоставленные строки), -n требует ставить ее номер в исходном файле перед каждой выводимой строкой (нумерация строк начинается с 1).

    Опции -i, -v и -x воздействуют на процесс сопоставления: -i предписывает не различать при сопоставлении большие и малые буквы, -v - выбирать строки, не удовлетворяющие ни одному из заданных шаблонов, -x - рассматривать только строки, все символы которых участвуют в успешном сопоставлении с одним из шаблонов.

    Опция -s подавляет выдачу диагностических сообщений о том, что исходный файл не существует или не доступен на чтение.

    Отметим, что опции -F и -q, каждая по-своему, ускоряют работу служебной программы grep: -F упрощает сопоставление, -q позволяет завершить действие после первого успешного сопоставления (и не обрабатывать оставшиеся строки и/или файлы). Сочетание опций -q и -s позволяет также более свободно задавать исходные файлы, не заботясь об их существовании и доступности.

    Рассмотрим примеры использования утилиты grep. Для выборки пустых строк из файла стандартного ввода пригодны два шаблона:

    grep ^$
    grep -v .

    Если нужно выбрать строки, имеющие вид abc или def, можно воспользоваться одной из трех команд:

    grep -E '^abc$|^def$'
    grep -F -x 'abc
    def'

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

    grep -i '^[^C].* CALL ' *.for

    В командных файлах для обработки текстов часто используется потоковый редактор   sed:

    sed [-n] сценарий [файл ...]
    sed [-n] [-e сценарий] ... 
        [-f файл_сценария] ... [файл ...]

    Редактор sed читает указанные текстовые файлы (по умолчанию - стандартный ввод), выполняет редактирование в соответствии с командами сценария и записывает результат на стандартный вывод. Смысл опций -e и -f аналогичен утилите grep. Опция -n подавляет подразумеваемый вывод и предписывает выдавать только явно отобранные строки.

    Сценарий для sed состоит из редактирующих команд (каждая на отдельной строке), имеющих следующий формат:

    [адрес [, адрес]] функция [аргумент ...]

    Функция имеет здесь однобуквенное обозначение.

    В нормальном режиме sed циклически выполняет следующие действия:

  • Добавляет входную строку в буфер. Обычно буфер пуст, если только предыдущий цикл не завершился командой D.
  • Применяет к буферу последовательно все команды сценария, адреса в которых позволяют их применить.
  • Если не указана опция -n, копирует буфер на стандартный вывод, добавив в конце перевод строки. Очищает буфер.
  • Некоторые команды используют хранилище, чтобы запомнить весь буфер или его часть для последующего применения.

    Адрес в редактирующей команде sed - это либо десятичное число, означающее номер входной строки в совокупности входных файлов, либо символ $, который обозначает последнюю входную строку, либо контекстный адрес, имеющий вид /базовое_регулярное_выражение/. Контекстный адрес задает первую (начиная с текущей) из строк, успешно сопоставленных с БРВ при движении вперед.

    Командная строка без адреса применима к любому буферу, командная строка с одним адресом - к буферу с соответствующим адресом, командная строка с двумя адресами - к буферам с адресами в диапазоне от первого до второго включительно; затем процесс повторяется, начиная с первой строки вслед за выбранным диапазоном.

    Перечислим команды редактора sed. В скобках указывается максимальное число допустимых адресов для каждой функции.

    Аргумент текст состоит из одной или более строк. Все строки, кроме последней, заканчиваются на \, чтобы экранировать символ перевода строки.

    (2){ функция
    функция
    ...
    }

    Выполнить заданную последовательность функций.

    (1)a\
    текст

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

    (2)b [метка]

    Перейти к команде :, содержащей метку. Если метка пуста, перейти на конец сценария.

    (2)c\ текст

    Заменить. Удалить содержимое буфера. При 0 или 1 адресе или в конце двухадресного диапазона вывести текст. Начать новый цикл.

    (2)d

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

    (2)D

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

    (2)g

    Заменить содержимое буфера содержимым хранилища.

    (2)G

    Добавить к содержимому буфера содержимое хранилища.

    (2)h

    Заменить содержимое хранилища содержимым буфера.

    (2)H

    Добавить к содержимому хранилища содержимое буфера.

    (1)i\
    текст

    Вставить. Вывести текст.

    (2)l

    Вывести буфер, заменяя непечатные символы на пары символов ASCII и разбивая длинные строки.

    (2)n

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

    (2)N

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

    (2)p

    Скопировать буфер на стандартный вывод.

    (2)P

    Скопировать начальный сегмент буфера (до первого перевода строки) на стандартный вывод.

    (1)q

    Выйти. Перейти на конец сценария. Нового цикла не начинать.

    (2)r ч_файл

    Прочитать содержимое ч_файла. Поместить его на стандартный вывод перед чтением следующей входной строки.

    (2)s/БРВ/замена/флаги

    Подставить замену вместо фрагментов буфера, отождествленных с БРВ. Флаги могут быть опущены или иметь следующие значения:

  • число n - заменить n-е вхождение БРВ ;
  • g - заменить все вхождения БРВ, а не только первое;
  • p - если замена произошла, вывести содержимое буфера;
  • w з_файл - если замена произошла, добавить содержимое буфера к з_файлу.
  • Вместо символа в замене подставляется цепочка, отождествленная с БРВ.

    (2)t [метка]

    Проверить. Перейти к команде :, содержащей метку, если со времени последнего чтения входной строки или последнего выполнения команды t в буфере производились подстановки. Если метка пуста, перейти на конец сценария.

    (2)w з_файл

    Записать. Добавить содержимое буфера к з_файлу.

    (2)x

    Обменять содержимое буфера и хранилища.

    (2)y/цепочка1/цепочка2/

    Заменить все символы буфера, содержащиеся в цепочке1, на соответствующие символы цепочки2. Длины цепочек должны совпадать.

    (2)! функция

    Отрицание. Применить функцию (или группу, если функция начинается с { ) только к строкам, не соответствующим адресам.

    (0): метка

    Не делает ничего. Содержит лишь метку, на которую может быть осуществлен переход командами t или b.

    (1)=

    Вывести в качестве отдельной строки номер текущей строки.

    (0)

    Пустая команда.

    (0)#

    Управляющий комментарий. Если сценарий начинается с символов #n, подразумеваемый вывод подавляется (что эквивалентно опции -n в командной строке). В остальных случаях игнорировать # и остаток строки.

    Последовательность символов \n успешно сопоставляется с переводом строки. Явный символ перевода строки не должен использоваться в БРВ контекстных адресов и функции замены.

    Приведем примеры использования потокового редактора   sed. В процессе загрузки ОС Linux выполняются командные строки, аналогичные показанным в листинге 6.23.

    map=`basename $map | sed -e s/^auto_home/auto.home/ -e s/^auto_mnt/auto.mnt/`
    cat /etc/auto.master | grep -v '^+' | sed -e '/^#/d' -e '/^$/d'

    Первая из них заменяет подчеркивание на точку в именах файлов, обслуживающих автомонтирование файловых систем, вторая отсеивает строки файла auto.master, начинающиеся с символа + (это делает grep -v ), комментарии (строки, начинающиеся символом #) и пустые строки.

    Следующий вызов sed (см. листинг 6.24) сжимает несколько идущих подряд пустых строк в одну.

    sed -n '
    p
    /^$/ {
    # Текущая строка - пустая.
    # Добавляем следующие строки к буферу, 
    # пока он остается пустым.
    # Тем самым игнорируются "лишние" пустые 
    # строки.
    :Empty
       n
       /^$/ b Empty
    # Добавленная строка оказалась непустой. 
    # Выведем ее.
       p
    }
    '

    Любопытно сопоставить приведенный нами сценарий с примером, включенным в текст стандарта POSIX-2001 (см. листинг 6.25). Наш вариант явно проще и короче.

    sed -n '
    # Выведем непустые строки
    /./ {
       p
       d
    }
    # Выведем одну пустую строку, затем 
    # проанализируем следующие.
    /^$/ p
    # Прочитаем следующую строку, отбросим 
    # оставшийся перевод строки (пустую строку)
    # и вернемся к проверке пустой строки.
    :Empty
    /^$/ {
       N
       s /.//
       b Empty
    }
    # Выведем непустую строку, затем вернемся к 
    # поиску первой пустой.
    p
    '

    Еще одно популярное средство обработки текстовых файлов - служебная программа awk:

    awk [-F РРВ] [-v присваивание] ... 
        программа [аргумент ...]
    awk [-F РРВ] -f программный_файл ... 
        [-v присваивание] ... [аргумент ...]

    Утилита awk выполняет программы, написанные на одноименном языке программирования, специально предназначенном для обработки текстов. Программа на языке awk представляет собой последовательность шаблонов и соответствующих действий, которые выполняются при чтении входных данных, успешно сопоставляющихся с шаблоном. Оператор шаблон-действие имеет вид

    шаблон { действие }

    Ввод для awk делится на записи, разделяемые специальным символом. По умолчанию это перевод строки; в таком случае awk обрабатывает ввод построчно. Разделитель записей можно изменить, переопределив переменную   RS. Каждая запись делится на поля, ограниченные разделителями полей (по умолчанию - пробелами или табуляциями). Любой из них можно изменить, переопределив переменную   FS или указав опцию -F с аргументом - расширенным регулярным выражением (РРВ). Поля исходных строк доступны по именам $1, $2,...; $0 - вся входная строка.

    Каждая исходная строка сопоставляется с каждым из шаблонов; в случае успеха выполняются указанные действия. После сопоставления со всеми шаблонами вводится следующая строка и процесс сопоставления повторяется. Может быть опущен либо шаблон, либо действие, но не оба вместе. Если для данного шаблона не указаны действия, то строка просто копируется на стандартный вывод. Если для действия не определен шаблон, то оно будет выполняться для каждой входной строки. Строки, которые не удалось сопоставить ни одному шаблону, игнорируются.

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

  • if ( условие ) оператор [ else оператор ];
  • while ( условие ) оператор;
  • for ( выражение; условие; выражение ) оператор;
  • break;
  • continue;
  • { [ оператор ] ... };
  • переменная = выражение # оператор присваивания;
  • print [ список_выражений ] [> выражение ];
  • printf формат [, список_выражений ] [> выражение ];
  • next # пропустить оставшиеся шаблоны и перейти к следующей строке;
  • exit # пропустить оставшиеся строки.
  • Операторы завершаются точкой с запятой, переводом строки или правой скобкой. Пустой список_выражений означает всю строку. Выражения строятся из цепочек символов и чисел с помощью операций +, -, *, /, %, ^ (возведение в степень) и конкатенации (обозначается пробелом). В них также можно использовать операции из языка C: ++, --, +=, -=, *=, /=, %=, ^=, ?: (условное выражение ). Переменные инициализируются пустыми цепочками, могут быть скалярами, элементами массива (обозначается x[i] ) или полями. Индексами массива служат любые (не обязательно числовые) цепочки символов, что позволяет реализовать разновидность ассоциативной памяти. Цепочки символов заключаются в двойные кавычки (").

    Оператор   print выдает свои аргументы на стандартный вывод (или в файл, если присутствует часть >выражение), разделяя их текущим разделителем полей и завершая каждую запись выходным разделителем записей. Оператор   printf делает то же, но под управлением формата.

    Язык awk содержит большое число встроенных функций. Кратко опишем их.

    Математические функции atan2 (y, x), cos (x), sin (x), exp (x), log (x), sqrt (x) не нуждаются в пояснениях. Функция int (x) отбрасывает дробную часть своего аргумента, rand () возвращает псевдослучайное число в диапазоне от

    В число функций, оперирующих цепочками символов, входят gsub (РРВ, замена[, цепочка]) и sub (РРВ, замена[, цепочка]) - соответственно, глобальная и однократная замена вхождений РРВ в $0 или цепочку, по аналогии с командой s редактора sed и ее флагом g; index ( цепочка, подцепочка ) - поиск подцепочки в цепочке ; length [([цепочка])] - вычисление длины цепочки-аргумента или $ ); match> (цепочка, РРВ) - поиск вхождения РРВ в цепочку с установкой значений переменных   RSTART и RLENGTH (см. далее); split (цепочка, массив[, РРВ-разделитель]) - расщепление цепочки по полям в элементы массива); sprintf (формат, выражение, выражение, ...) - формирование цепочки символов средствами форматного вывода; substr (цепочка, m[, n]) - выделение n-символьной подцепочки, начинающейся с позиции m ; tolower (цепочка) - приведение к строчным буквам; toupper (цепочка) - приведение к прописным буквам.

    В языке awk имеются также группа функций ввода/вывода и функции общего назначения. Функция close (выражение) закрывает файл или канал, поименованный заданным выражением, getline [переменная] обеспечивает чтение записи из текущего входного файла (возможно использование конвейера вида выражение   | getline [переменная] и перенаправление ввода getline [переменная] < выражение ), system (выражение) - выполнение команды, заданной выражением.)

    Язык awk допускает определение пользовательских функций, для чего служит конструкция

    function имя_функции ([аргумент, ...]) 
        { операторы }

    Шаблон в языке awk - это произвольная логическая комбинация, составленная с помощью операций !, ||, и скобок из расширенных регулярных выражений и выражений сравнения. РРВ обрамляются символами /. Отдельное РРВ в шаблоне сопоставляется со всей строкой. РРВ допускаются и в выражениях сравнения. Шаблон может состоять из двух шаблонов, разделенных запятой; указанные действия выполняются для всех строк между строкой, удовлетворяющей первому шаблону, и строкой, удовлетворяющей второму.

    Выражение сравнения - одна из следующих конструкций:

    выражение опер_сопост РРВ
    выражение опер_сравн выражение

    Здесь опер_сравн - любая из шести операций сравнения языка C, опер_сопост - это ~ (успешно сопоставляется) или !~ (не сопоставляется).

    Условие - арифметическое выражение, выражение сравнения или их логическая комбинация.

    Для выполнения каких-либо действий перед чтением первой или после чтения последней исходной строки определены специальные шаблоны BEGIN и END. Шаблон BEGIN следует указывать первым, END - последним.

    Присваивания, заданные в командной строке с помощью опции -v, выполняются до начала интерпретации awk-программы (в частности, до действий, ассоциированных с шаблоном BEGIN ). Например, для использования символа c в качестве разделителя полей можно указать в командной строке -v 'FS = c'.

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

    Перечислим специальные переменные awk.

    ARGC

    Число элементов в массиве ARGV.

    ARGV

    Массив аргументов командной строки awk, исключая опции и программы.

    CONVFMT

    Формат для преобразования чисел в цепочки символов (кроме операторов вывода, где используется переменная   OFMT, см. далее). По умолчанию - %.6g.

    ENVIRON

    Массив, представляющий окружение. Индексами служат цепочки символов, совпадающих с именами переменных окружения.

    FILENAME

    Имя файла, из которого в данный момент производится ввод.

    FNR

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

    FS

    РРВ - разделитель полей во входных данных, по умолчанию - пробел.

    NF

    Количество полей в текущей записи.

    NR

    Порядковый номер текущей записи, считая от начала обработки исходных данных.

    OFMT

    Формат вывода чисел, по умолчанию %.6g.

    OFS

    Разделитель полей при выводе, по умолчанию - пробел.

    ORS

    Разделитель записей при выводе, по умолчанию - перевод строки.

    RLENGTH

    Длина успешно сопоставленной функцией match() цепочки символов.

    RS

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

    RSTART

    Начальная позиция успешно сопоставленной функцией match()   цепочки символов (считая от 1).

    SUBSEP

    Цепочка символов - разделитель индексов многомерных массивов; подразумеваемое значение зависит от реализации.

    Приведем примеры использования утилиты awk. Сложить числа, стоящие в первом столбце исходного файла, вывести сумму и среднее арифметическое позволяет awk-программа, показанная в листинге 6.26.

    { s += $1 } END { print "Сумма:", s, 
    " Среднее арифметическое:", s/NR }

    Командная строка из листинга 6.27, служит для вывода тех строк файла f1.txt, у которых первое поле не совпадает с первым полем предыдущей строки.

    awk '$1 != prev { print; prev = $1 }' f1.txt

    Чтобы распечатать файл f2.txt, вставляя после слова "Page" номера страниц (начиная с первой), можно воспользоваться awk-программой (предполагается, что она помещена в файл prog.awk ) и командной строкой, представленными, соответственно, в листингах 6.28 и 6.29.

    /Page/ { $2 = n++ } { print }
    awk -f prog.awk -v 'n=1' f2.txt

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

    { for (i = NF; i > 0; --i) print $i }

    Промоделировать работу утилиты echo можно с помощью awk-программы (см. листинг 6.31).

    BEGIN {
       for (i = 1; i < ARGC; ++i)
       printf ("%s%s", ARGV [i], 
               i == ARGC - 1 ? "\n" : " ")
    }

    Следующая awk-программа (см. листинг 6.32) позволяет разложить список поиска, хранящийся в переменной окружения PATH, по элементам массива.

    BEGIN {
       n = split (ENVIRON ["PATH"], path, ":")
       for (i = 1; i <= n; ++i)
          print path [i]
    }

    В листинге 6.33 приведен фрагмент командного файла, выполняемого при выключении системы. Здесь можно обратить внимание на разные виды экранирования. (Третье поле в выдаче mount - это точка монтирования.)

    # Перемонтируем на чтение все, что еще остается смонтированным.
    mount | awk '/( \/ |^\/dev\/root)/ { print $3 }' | while read line; do
       mount -n -o ro,remount $line
    done

    Отметим, что в POSIX-2001 стандартизована весьма развитая версия awk, входной язык которой приближен к языку C.

    На уровне функций работа с регулярными выражениями поддержана семейством regex (см. листинг 6.34).

    #include <regex.h>
    int regcomp (regex_t *restrict preg, 
        const char *restrict pattern, int cflags);
    int regexec (const regex_t *restrict preg, 
        const char *restrict string, 
        size_t nmatch, regmatch_t 
    	pmatch [restrict], int eflags);
    void regfree (regex_t *preg);
    size_t regerror (int errcode, const regex_t
        *restrict preg, char *restrict errbuf, 
    	size_t errbuf_size);

    Первый член этого семейства, функция regcomp(), компилирует регулярное выражение, заданное аргументом pattern, и помещает результат компиляции в структуру типа regex_t, на которую указывает аргумент preg. Эта структура, описанная в заголовочном файле <regex.h>, должна содержать про крайней мере поле

    size_t re_nsub; 
    /* Число заключенных в скобки подвыражений */

    Третий аргумент функции regcomp(), cflags, задается как побитное ИЛИ следующих флагов:

    REG_EXTENDED

    Использовать расширенные регулярные выражения (подразумеваемый тип регулярных выражений - базовые ).

    REG_ICASE

    При сопоставлении не различать большие и малые буквы.

    REG_NOSUB

    В regexec() сообщать только об успехе/неудаче сопоставления (и не устанавливать значения поля   re_nsub структуры regex_t ).

    REG_NEWLINE

    Изменить трактовку переводов строк (мы не будем на этом останавливаться).

    Функция regexec() сопоставляет цепочку символов   string со скомпилированным шаблоном, заданным аргументом preg. При успешном выполнении результат равен нулю; в противном случае возвращается ненулевое значение, свидетельствующее о неудаче сопоставления или ошибке. Аргумент eflags - побитное ИЛИ флагов REG_NOTBOL и REG_NOTEOL - определяет, являются ли границы цепочки границами строки, что важно для обработки фиксаторов ^ и $. Если значение аргумента nmatch равно нулю или при вызове regcomp() был задан флаг REG_NOSUB, аргумент pmatch функции regexec() игнорируется. В противном случае он должен указывать на массив не менее чем из nmatch элементов, который будет заполнен смещениями подцепочек, сопоставленных с заключенными в скобки подвыражениями шаблона ( pmatch [0] соответствует всему регулярному выражению, в неиспользуемые элементы помещается -1).

    Структурный тип regmatch_t должен включать по крайней мере следующие поля:

    regoff_t rm_so; 
    /* Смещение в байтах начала подцепочки 
    от начала цепочки */
    
    regoff_t rm_eo; 
    /* Смещение в байтах первого символа за 
    концом подцепочки от начала цепочки */

    Тип regoff_t определяется как целое со знаком, способное вместить любое значение типов off_t и ssize_t.

    Функция regfree() освобождает память, запрошенную вызовом regcomp() с тем же значением аргумента preg, которое после этого нельзя использовать как указатель на скомпилированное регулярное выражение.

    В файле <regex.h> определены константы, возвращаемые функциями семейства regex() в случае ошибки. Например, значение REG_NOMATCH возвращается функцией regexec() при неудаче сопоставления, REG_BADPAT обозначает некорректное регулярное выражение, REG_ESPACE - нехватку памяти и т.д. Функция regerror() отображает эти константы в неспецифицируемые стандартом цепочки печатных символов и помещает их в буфер errbuf. Приложение, вызывая regerror(), должно передать в качестве аргумента errcode последнее ненулевое значение, возвращенное функциями regcomp() или regexec() с заданным значением аргумента preg.

    Приведем пример использования функций семейства regex() (см. листинг 6.35). Обратим внимание на задание флага REG_NOTBOL при повторных обращениях к regexec().

    #include <stdio.h>
    #include <limits.h>
    #include <regex.h>
    
    /* Программа ищет все вхождения заданного шаблона во всех входных строках */
    /* и выводит успешно сопоставленные подцепочки */
    
    #define PATTERN "[A-Za-z][A-Za-z0-9]{0,31}"
    
    int main (void) {
       char line [LINE_MAX];     /* Буфер для входных строк */
       char *pline;              /* Указатель на начало сопоставляемой части строки */
       regex_t cere;             /* Скомпилированное расширенное регулярное выражение */
       regmatch_t pm;            /* Структура для запоминания границ сопоставленной подцепочки */
       int reerrcode;            /* Код ошибки от regcomp или regexec */
       char reerrbuf [LINE_MAX]; /* Буфер для строк с сообщениями об ошибках */
       int i;
    
       if ((reerrcode = regcomp (cere, PATTERN, REG_EXTENDED)) != 0) {
            (void) regerror (reerrcode, cere, reerrbuf, sizeof (reerrbuf));
            fputs (reerrbuf, stderr);
            fputc ('\n', stderr);
            regfree (cere);
            return (reerrcode);
       }
    
       fputs ("Вводите строки, сопоставляемые с шаблоном " PATTERN "\n", stdout);
       while (fgets (line, sizeof (line), stdin) != NULL) {
       /* Произведем первое сопоставление с прочитанной строкой. */
       /* Оно отличается от остальных при наличии в шаблоне фиксатора начала */
       reerrcode = regexec (cere, pline = line, 1, pm, 0);
          while (reerrcode == 0) {
              /* Повторяем, пока сопоставления с остатком строки успешны */
              fputs ("Сопоставленная подцепочка: ", stdout);
              for (pline += pm.rm_so, i = pm.rm_eo - pm.rm_so; i-- > 0; ) {
                  fputc (*pline++, stdout);
              }
              fputc ('\n', stdout);
              reerrcode = regexec (cere, pline, 1, pm, REG_NOTBOL);
           }
        }
    
        regfree (cere);
        return (ferror (stdin) || ferror (stdout));
    }

    Читателю рекомендуется поэкспериментировать с шаблоном PATTERN, пробуя различные варианты, в том числе некорректные.

    Идейно простым, но весьма мощным и полезным средством обработки текстовых файлов является служебная программа преобразования символов tr:

    tr [-c | -C] [-s] цепочка1 цепочка2
    tr -s [-c | -C] цепочка1
    tr -d [-c | -C] цепочка1
    tr -ds [-c | -C] цепочка1 цепочка2

    Она не применяет регулярных выражений как таковых, но в цепочках - аргументах можно употреблять конструкции, аналогичные некоторым односимвольным РВ.

    Утилита tr копирует стандартный ввод на стандартный вывод с заменой либо удалением выбранных символов. При отсутствии опций введенные символы, найденные в цепочке1, заменяются на соответствующие (стоящие на тех же относительных позициях) символы из цепочки2. Опции -c и -C предписывают использовать вместо цепочки1 ее дополнение до множества всех символов; в первом случае дополнение упорядочивается в соответствии с кодировкой, во втором - по алфавиту. По опции -d будут удалены все входные символы, заданные цепочкой1. Опция -s задает сжатие (до одного) последовательностей одинаковых символов, специфицированных последней из цепочек, указанных в командной строке ( сжатие производится после каждой замены и/или удаления).

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

    c1-c2

    Обозначает цепочку символов, лежащих в диапазоне от c1 до c2 включительно.

    [:класс_символов:]

    Обозначает цепочку символов, принадлежащих указанному классу (см. выше описание базовых регулярных выражений ).

    [=класс_эквивалентности=]

    Обозначает цепочку символов, принадлежащих указанному классу эквивалентности при алфавитном сравнении.

    [c*n]

    Обозначает символ c, повторенный n раз. Может использоваться только в цепочке2. Если первая цифра в n есть 0, n рассматривается как восьмеричное число; иначе - как десятичное. Нулевое или отсутствующее n воспринимается как "очень много"; эта возможность полезна при дополнении цепочки2 до длины цепочки1.

    Обратный слэш можно использовать для задания управляющих символов ( '\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v' ). Кроме того, \ обозначает код символа, если за ним идут одна, две или три восьмеричные цифры.

    Следующая команда (см. листинг 6.36) помещает список всех слов из файла f1, по одному на строку, в файл f2 (под словом понимается максимальная последовательность букв).

    tr -cs '[:alpha:]' '[\n*]' < f1 > f2

    Команда, показанная в листинге 6.37, переводит большие буквы в малые, попутно сжимая последовательности одинаковых (без учета регистра) букв.

    tr -s '[:upper:]' '[:lower:]'

    Служебная программа uniq

    uniq [-c | -d | -u] [-f число] [-s число] 
         [входной_файл [выходной_файл]]

    позволяет сократить до одной подряд идущие одинаковые строки (сделать одинаковые строки файла смежными можно с помощью утилиты sort ). Опции предоставляют дополнительный сервис.

    -c

    Перед каждой выходной строкой помещать ее кратность во входном файле.

    -d

    Подавить вывод неповторяющихся строк.

    -f число

    При сравнении строк игнорировать заданное число начальных полей. Поле определяется как максимальная цепочка символов, успешно сопоставляемая с базовым регулярным выражением   [[:blank:]]*[^[:blank:]]* .

    -s число

    При сравнении строк игнорировать заданное число начальных символов. При совместном использовании опций -f и -c игнорируется указанное число символов, идущих после заданного числа полей.

    -u

    Подавить вывод строк, повторявшихся во входном файле.

    В качестве примера употребления утилиты uniq приведем конвейер, позволяющий найти десять самых употребительных заголовочных файлов среди включаемых в стандартные заголовочные файлы, расположенные в каталоге   /usr/include и его подкаталогах (см. листинг 6.38). Результат работы конвейера может выглядеть так, как показано в листинге 6.39.

    find /usr/include -name '*.h' -exec cat 
         {} \; | tr -d '[:blank:]' | \
    grep -E -e '^#include(<.*>|".*")' 
         | sort | uniq -dc | sort -r | head
    977 #include"nsISupports.h"
    315 #include<glib.h>
    201 #include<gdk/gdk.h>
    167 #include<glibmm.h>
    160 #include<features.h>
    154 #include<glib-object.h>
    144 #include"nsCOMPtr.h"
    139 #include<sys/types.h>
    139 #include<glibmm/class.h>
    135 #include"nscore.h"

    Аналоги реляционных операций для текстовых файлов

    Служебная программа cut

    cut -b список [-n] [файл ...]
    cut -c список [файл ...]
    cut -f список [-d разделитель] 
        [-s] [файл ...]

    используется для выборки байт (опция -b ), символов ( -c ) либо ограниченных разделителями полей (опция -f ) с заданными списком номерами из строк исходных файлов, их конкатенации и выдачи на стандартный вывод. Если применить терминологию реляционных баз данных, cut выполняет операцию проекции отношения.

    Список, являющийся аргументом опций -b, -c и -f, задается как последовательность разделенных запятыми или пробелами положительных чисел или диапазонов (с естественными умолчаниями, когда границы опущены). Опция -d определяет разделитель полей (по умолчанию это символ табуляции), -n предписывает не разрывать (многобайтные) символы, опция -s в сочетании с -f подавляет вывод строк, в которых не оказалось символов-разделителей (по умолчанию такие строки копируются на стандартный вывод без изменений).

    Пример. Чтобы выделить из базы данных пользователей входные имена и идентификаторы, можно воспользоваться командой, показанной в листинге 6.40. Начальный фрагмент возможного результата приведен в листинге 6.41.

    cut -d : -f 1,3 /etc/passwd
    root:0
    bin:1
    daemon:2
    adm:3
    lp:4
    sync:5
    . . .

    Служебная программа paste

    paste [-s] [-d список] файл ...

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

    В качестве имени исходного файла может быть задан минус, что означает стандартный ввод. Если минус употреблен многократно, то стандартный ввод читается построчно, циклически по именам - минусам. Любопытно отметить, что стандарт POSIX-2001 предписывает реализациям поддерживать обработку не менее двенадцати исходных файлов.

    Рассмотрим несколько примеров. Для выдачи в четыре столбца имен файлов текущего каталога можно воспользоваться конвейером, показанным в листинге 6.42.

    ls | paste - - - -

    Для попарного слияния последовательных строк файла целесообразно "сыграть" на том, что список разделителей - кольцевой (см. листинг 6.43).

    paste -s -d "\0\n" f.txt

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

    cut -b 1-80 -n f > f1.txt
    cut -b 81- -n f > f2
    . . .
    paste -d '\0' f1.txt f2 > f3

    Еще один оператор реляционной СУБД - join - выдает на стандартный вывод результат соединения двух отношений, задаваемых строками файла1 и файла2:

    join [-a номер_файла | -v номер_файла] 
       [-e цепочка] [-o список] [-t символ]
       [-1 номер_поля] [-2 номер_поля] файл1 файл2

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

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

    Как правило, поля разделяются символами пробела, табуляции или перевода строки. В этом случае несколько последовательных разделителей интерпретируются как один и начальные разделители отбрасываются. Подразумеваемым разделителем при выводе является пробел.

    Допустимы следующие опции.

    -a номер_файла

    В дополнение к обычному выводу выдать строку для каждой непарной строки из файла с указанным номером (1 или 2).

    -e цепочка

    Заменить пустые поля вывода из списка опции -o заданной цепочкой символов.

    -o список

    Составлять выходные строки из полей, заданных в списке. Элемент списка имеет вид номер_файла.номер_поля или

    -t символ

    Использовать символ в качестве разделителя. Каждое вхождение символа в строку значимо. Указанный символ используется как разделитель и при вводе, и при выводе.

    -v номер_файла

    Вместо подразумеваемого вывода выдавать только непарные строки из файла с указанным номером (1 или 2).

    -1 номер_поля

    Производить соединение по полю   файла1 с заданным номером.

    -2 номер_поля

    Производить соединение по полю   файла2 с заданным номером.

    Рассмотрим примеры. Командная строка (см. листинг 6.45) выполняет соединение баз данных пользователей и групп, отсортированных в порядке возрастания идентификаторов групп. На стандартный вывод поступают входные имена, имена групп и основные каталоги (см. листинг 6.46).

    join -1 4 -2 3 -o 1.1,2.1,1.6 -t : 
         passwd.sorted group.sorted
    halt:root:/sbin
    operator:root:/root
    root:root:/root
    shutdown:root:/sbin
    sync:root:/sbin
    bin:bin:/bin
    daemon:daemon:/sbin
    . . .

    Пусть имеется два упорядоченных по алфавиту справочника: номера телефонов и адреса электронной почты (см. листинг 6.47). Предполагается, что в качестве разделителя полей применяется символ табуляции. Тогда командная строка, показанная в листинге 6.48, позволит получить объединенный справочник (см. листинг 6.49).

    Имя Номер телефона
    Иван 123-4567
    Петр 123-5678
    Яков 123-6789
    
    Имя Адрес электронной почты
    Иван ivan123@mail.ru
    Олег oleg@yahoo.com
    Яков yak@yandex.ru
    join -t '<tab>' -a 1 -a 2 -e '---------' 
         -o 0,1.2,2.2 phone.txt email.txt
    Имя Номер телефона Адрес электронной почты
    Иван 123-45-67 ivan123@mail.ru
    Олег --------- oleg@yahoo.com
    Петр 123-56-78 ---------
    Яков 123-67-89 yak@yandex.ru

    Читателю предлагается самостоятельно выбрать правильный способ задания символа табуляции в качестве аргумента опции -t служебной программы join, а также объяснить, зачем нужен пробел в начале заголовка каждого из справочников.

    Средства обработки каталогов

    Обработка каталогов, как и обычных файлов, начинается с их открытия. Для этого предназначена функция opendir() (см. листинг 6.50).

    #include <dirent.h>
    DIR *opendir (const char *dirname);

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

    #include <dirent.h>
    void rewinddir (DIR *dirp);

    Чтение элементов каталога выполняет функция readdir() (см. листинг 6.52), которая возвращает указатель на структуру, представляющую текущий элемент каталога ; после ее завершения текущим становится следующий элемент достижении конца каталога и в случае ошибки возвращается пустой указатель. Следовательно, если приложению необходимо различать обе ситуации, оно должно обнулить значение переменной   errno перед вызовом readdir(), а затем, если результат равен NULL, проанализировать это значение.

    #include <dirent.h>
    struct dirent *readdir (DIR *dirp);

    Согласно стандарту POSIX-2001, структура dirent содержит по крайней мере одно поле:

    char d_name []; /* Имя файла */

    В качестве необязательного описано еще одно поле:

    ino_t d_ino; /* Порядковый номер файла */

    Если элемент каталога представляет символьную ссылку, значение этого поля не определено.

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

    После завершения работы с каталогом его следует закрыть с помощью функции closedir() (см. листинг 6.53), возвращающей

    #include <dirent.h>
    int closedir (DIR *dirp);

    Нередко чтение элементов каталога и сопоставление с шаблоном имен файлов сочетаются (см. выше раздел "Генерация маршрутных имен файлов" ). Подобное сопоставление реализует функция fnmatch() (см. листинг 6.54).

    #include <fnmatch.h>
    int fnmatch (const char *file_pattern, 
       const char *file_name, int flags);

    На процесс сопоставления имени file_name с шаблоном   file_pattern влияют следующие флаги:

    FNM_PATHNAME

    Трактовать имя file_name как маршрутное. Символу / в имени должен явным образом сопоставляться этот же символ в шаблоне (а не ? или выражение в квадратных скобках). Если флаг не установлен, символ / трактуется наравне с другими.

    FNM_NOESCAPE

    При наличии этого флага символ \ трактуется наравне с другими. В противном случае он играет экранирующую роль.

    FNM_PERIOD

    Если этот флаг установлен, точка в начале имени файла должна сопоставляться с точкой в шаблоне. Иначе точка в начале имени сопоставляется на общих основаниях.

    В случае успешного сопоставления функция fnmatch() возвращает ноль, при неудаче результат равен FNM_NOMATCH, в случае ошибки возвращается другое ненулевое значение.

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

    #include <dirent.h>
    #include <fnmatch.h>
    #include <errno.h>
    #include <stdio.h>
    
    /* Программа сопоставляет имена файлов текущего каталога с заданными шаблонами */
    
    #define SEARCH_DIR "."
    
    static void match_names (DIR *dirp, const char *pattern) {
       struct dirent *dp;
    
       rewinddir (dirp);
    
       while (errno = 0, (dp = readdir (dirp)) != NULL) {
          if (fnmatch (pattern, dp->d_name, FNM_PERIOD) == 0) {
            (void) printf (" %s\n", dp->d_name);
          }
       }
    
       if (errno != 0) {
           perror ("Ошибка при чтении каталога " SEARCH_DIR);
       }
    }
    
    int main (int argc, char *argv []) {
       DIR *dirp;
       int i;
    
       if ((dirp = opendir (SEARCH_DIR)) == NULL) {
          perror ("Ошибка при открытии каталога " SEARCH_DIR);
          return (-1);
       }
    
       for (i = 1; i < argc; i++) {
         (void) printf ("Файлы каталога " SEARCH_DIR ", удовлетворяющие шаблону %s\n", argv [i]);
         match_names (dirp, argv [i]);
       }
    
       return (closedir (dirp));
    }

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

    #include <glob.h>
    int glob (const char *restrict file_pattern, 
      int flags, int (*errfunc) (const char 
      *epath, int eerrno), glob_t *restrict pglob);
    void globfree (glob_t *pglob);

    Функция glob() сопоставляет все доступные маршрутные имена с шаблоном   file_pattern и генерирует список успешно сопоставленных имен. Результаты своей работы функция glob() возвращает в структуре типа glob_t, содержащей, согласно стандарту, по крайней мере следующие поля:

    size_t gl_pathc; 
    /* Число успешно сопоставленных маршрутных 
    имен */
    
    char **gl_pathv; 
    /* Массив указателей на успешно сопоставленные 
    маршрутные имена */
    
    size_t gl_offs; 
    /* Число элементов, которые нужно зарезерви-
    ровать в начале массива gl_pathv */

    Структура, на которую указывает аргумент pglob, должна создаваться приложением. Другие области памяти, в том числе массив gl_pathv, резервируются функцией glob() по мере необходимости и освобождаются после обращения к globfree() с тем же значением pglob.

    Работой функции glob() можно управлять с помощью следующих флагов.

    GLOB_APPEND

    Добавлять вновь сгенерированные маршрутные имена к предыдущим результатам функции glob().

    GLOB_DOOFFS

    Принимать во внимание поле   gl_offs. Если этот флаг установлен, то pglob->gl_pathv будет указывать на pglob->gl_offs пустых указателей, за которыми следуют pglob->gl_pathc указателей на маршрутные имена и завершающий пустой указатель.

    GLOB_ERR

    Завершать работу по достижении каталога, который не удается открыть или прочитать. Обычно в таких случаях функция glob() продолжает процесс генерации маршрутных имен.

    GLOB_MARK

    Добавлять символ / после имен успешно сопоставленных файлов, являющихся каталогами.

    GLOB_NOCHECK

    При отсутствии успешно сопоставленных имен возвращать шаблон.

    GLOB_NOESCAPE

    Запретить экранирование символом \.

    GLOB_NOSORT

    Не сортировать сгенерированные имена по алфавиту.

    Если в процессе своей работы функция glob() достигает каталога, который не удается открыть или прочитать, а аргумент errfunc отличен от пустого указателя, то glob() вызывает (*errfunc ()) с двумя аргументами: "проблемным" маршрутным именем и соответствующим значением errno. Если этот вызов возвращает значение, отличное от нуля, или если установлен флаг GLOB_ERR, генерация маршрутных имен завершается, значения полей   pglob->gl_pathc и pglob->gl_pathv устанавливаются в соответствии с уже обработанными маршрутами, а результат вызова функции glob() полагается равным GLOB_ABORTED (при нормальном завершении возвращается 0).

    Перепишем программу, которая выводит имена файлов, удовлетворяющие заданным в командной строке шаблонам, воспользовавшись функцией glob() (см. листинг 6.57).

    #include <glob.h>
    #include <errno.h>
    #include <stdio.h>
    
    /* Программа выводит маршрутные имена, сгенерированные по заданным шаблонам */
    
    static int errfunc (const char *epath, int eerrno) {
        fprintf (stderr, "Ошибка при обработке каталога %s: ", epath);
        errno = eerrno;
        perror (NULL);
        return (0);
    }
    
    int main (int argc, char *argv []) {
        glob_t gl_buf;
        int i;
    
        for (i = 1; i < argc; i++) {
            (void) glob (argv [i], ((i == 1) ?
        }
    
        (void) printf ("Маршрутные имена, сгенерированные по заданным шаблонам:\n");
        for (i = 0; (unsigned) i < gl_buf.gl_pathc; i++) {
            (void) printf ("%s\n", gl_buf.gl_pathv [i]);
        }
    
        globfree (gl_buf);
        return (0);
    }

    Выше, при описании языка shell, мы рассматривали служебную программу basename. Ее логическим дополнением является утилита dirname:

    dirname цепочка_символов

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

    В качестве примера использования утилиты dirname приведем фрагмент командного файла, выполняемого при загрузке ОС Linux (см. листинг 6.58).

    if [ "`dirname $RAW`" = "/dev/raw" -a -f /dev/raw ]; then
       echo $" Please correct your /etc/sysconfig/rawdevices:"
       echo $" rawdevices are now located in the directory /dev/raw/ "
       echo $" If the command 'raw' still refers to /dev/raw as a file."
       echo $" you'll have to upgrade your util-linux package"
       exit
    fi

    Совместное использование утилит basename и dirname иллюстрирует листинг 6.59. Это командный файл с одним аргументом - маршрутным именем компилируемого C-файла, которое может быть задано как с расширением .c, так и без него.

    gcc -Wall -W -pedantic -o $(basename "$1" .c) 
         $(dirname "$1")/$(basename "$1" .c).c
    Вернуться к учебному плану