Linux и UNIX: программирование в shell. Руководство разработчика.

Тейнсли Дэвид

ЧАСТЬ 2

 

 

Фильтрация текста

 

ГЛАВА 7

 

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

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

В этой главе рассматриваются следующие темы:

   • создание шаблонов для поиска выражений, стоящих в начале или в конце строки;

   • создание шаблонов для поиска символов, встречающихся неопределенное число раз;

   • создание шаблонов для поиска специальных символов;

   • создание шаблонов для поиска символов из указанного набора или диапазона;

   • создание шаблонов для поиска символов, встречающихся указанное число раз подряд.

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

Регулярные выражения в той или иной форме используются всеми основными текстовыми редакторами и утилитами, выполняющими фильтрацию текста. К сожалению, наборы поддерживаемых выражений несколько различаются от программы к программе, но существуют так называемые базовые регулярные выражения, которые во всех программах обрабатываются одинаково. Именно их мы и рассмотрим в настоящей главе. Единственное исключение -oператор \{ \}, который поддерживается в программах sed и grep, но не в awk.

В табл. 7.1 перечислены метасимволы и операторы, применяемые в базовых регулярных выражениях.

Таблица 7.1. Метасимволы и операторы базовых регулярных выражений

^ Соответствует началу строки
$ Соответствует концу строки
[] Соответствует любому символу из числа заключенных в скобки; чтобы задать диапазон символов, укажите первый символ диапазона, дефис и последний символ (например, вместо шаблона [12345] можно ввести
[1-5])
[^] Соответствует любому символу, кроме тех, что указаны в скобках
\ Отменяет специальное значение следующего за ним метасимвола
. Соответствует любому отдельному символу
* Указывает на то, что предыдущий шаблон встречается ноль или более раз; в программах awk и egrep, где испольуются расширенные регулярные выражения, существует два дополнительных оператора:
? (означает, что предыдущий шаблон встречается не более одного раза) и
+ (означает, что предыдущий шаблон встречается один или более раз)
\{n\} Указывает на то, что предыдущий шаблон встречается ровно n раз
\{n,\} Указывает на то, что предыдущий шаблон встречается не менее п раз
\{,m\} Указывает на то, что предыдущий шаблон встречается не более т раз
\{n, m\} Указывает на то, что предыдущий шаблон встречается не менее n раз и не более т раз

 

7.1. Поиск одиночных символов с помощью метасимвола '.'

Метасимвол '.' соответствует любому одиночному символу. Если, например, требуется найти слово, начинающееся с подстроки "beg", после которой стоит произвольный символ, а за ним -cимвол 'n', задайте шаблон beg.n. Будут найдены такие слова, как "begin", "began" и т. д.

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

…x..x..x

Вот примеры отбора строк режима по этому шаблону:

drwxrwxrw- — не соответствует

-rw‑rw‑rw- — не соответствует

-rwxrwxr‑x -cоответствует

-rwxr‑xr‑x -cоответствует

Предположим, выполняется фильтрация текстового файла. Необходимо найти в нем строки, состоящие из десяти символов, из которых пятый и шестой — "ХС". Данная задача решается с помощью такого шаблона:

….ХС….

Он означает, что первые четыре символа могут быть произвольными, следующие два — "ХС", а последние четыре — тоже произвольные. Вот несколько примеров сравнения:

1234ХС9088 -cоответствует

4523ХХ9001 — не соответствует

0011ХА9912 — не соответствует

9931ХС3445 -cоответствует

 

7.2. Поиск выражений в начале строки с помощью метасимвола '^'

Метасимвол '^' позволяет искать слова или символы, стоящие в начале строки. Например, благодаря шаблону ^d можно отобрать из списка, выводимого командой ls -l, только те записи, которые соответствуют каталогам:

drwxrwxrw- - соответствует

-rw‑rw‑rw- - не соответствует

drwxrwxr‑x -cоответствует.

-rwxr‑xr‑x — не соответствует

Вернемся к рассмотренному в предыдущем параграфе примеру фильтрации текстового файла. Шаблон ^001 соответствует строкам, начинающимся с символов "001". Результат его применения может быть таким:

1234ХС9088 — не соответствует

4523ХХ9001 — не соответствует

0011ХА9912 -cоответствует

993IXC3445 — не соответствует

Для поиска строк, у которых в четвертой позиции от начала стоит символ '1', можно воспользоваться следующим шаблоном:

^…1

В результате получим:

1234ХС9088 — не соответствует

4b23ХХ9001 — не соответствует

0011XA9912 -cоответствует

993ДХС3445 -cоответствует

Чтобы найти строки, начинающиеся с символов "comp", следует указать:

^comp

Давайте немного усложним этот шаблон. Предположим, после символов "comp" могут идти любые две буквы, но завершать последовательность должны символы "ing":

^comp..ing

Этот шаблон обеспечивает поиск таких слов, как "computing", "complaining" и т. д. Как показывает данный пример, в регулярном выражении можно сочетать различные шаблоны поиска.

 

7.3. Поиск выражений в конце строки с помощью метасимвола '$'

Метасимвол "$' предназначен для поиска слов или символов, находящихся в конце строки. Он указывается в конце шаблона. Предположим, требуется найти строки, заканчивающиеся словом "trouble". Эту задачу позволяет решить такой шаблон:

trouble$

Следующий шаблон соответствует пустой строке, не содержащей символов:

А с помощью показанного ниже шаблона можно найти строки, включающие только один символ:

 

7.4. Поиск символов, встречающихся неопределенное число раз, с помощью метасимвола '*'

Метасимвол '*' означает, что предыдущий символ в регулярном выражении либо отсутствует, либо встречается произвольное число раз подряд (1, 2 и т. д.). Например, шаблон

сотрu*t

отвечает таким словам:

computer

computing

compuuuuute

А шаблон

10133*

соответствует следующему:

101333

10133

10134

 

7.5. Поиск специальных символов с помощью метасимвола '\'

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

$ . ' " * [ ] ^ | ( ) \ + ? { }

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

\.

Если необходимо найти файлы, допустим, с расширением pas, можно применить следующий шаблон:

\*\.pas

 

7.6. Поиск символов, входящих в заданный набор или

диапазон

Шаблон [] соответствует списку или диапазону символов, указанных в квадратных скобках.

Символы в списке можно разделять запятыми. Это облегчает восприятие шаблона, хотя и не является обязательным требованием.

Для задания диапазона символов используется дефис (-). Слева от него указывается первый символ диапазона, а справа — последний. Предположим, необходимо найти символ, являющийся цифрой. Можно применить такой шаблон:

[0123456789]

Однако проще задать диапазон:

[0-9]

Следующий шаблон соответствует любой строчной букве:

[a‑z]

Чтобы найти любую букву произвольного регистра, воспользуйтесь шаблоном

[A‑Za‑z]

Здесь формируется список из двух диапазонов: прописные буквы от 'А' до 'Z' и строчные буквы от 'а' до 'z'.

Представленный ниже шаблон соответствует любому алфавитно–цифровому символу:

[A‑Za‑z0-9]

Далее показан шаблон, предназначенный для поиска трехсимвольных комбинаций следующего типа: в начале находится буква 's', за ней может следовать любая прописная или строчная буква, а завершает последовательность буква 't':

s[a‑zA‑Z]t

Если же комбинация состоит только из букв нижнего регистра, воспользуйтесь таким шаблоном:

s[a‑z]t

Чтобы найти слово "computer" независимо от того, расположено оно в начале предложения или нет, примените такой шаблон:

[Сс]omputer

Следующий шаблон соответствует слову "system", которое начинается с прописной или строчной буквы и за которым следует точка:

[S,s]ystem\.

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

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

[A‑Za~z]*

Метасимвол '^' после открывающей квадратной скобки — это признак того, что шаблон соответствует любым символам, кроме указанных в скобках. Так, шаблон

[^a‑zA‑Z]

соответствует всем символам, кроме букв, а шаблон отвечает всем символам, которые не являются числами.

 

7.7. Поиск символов, встречающихся заданное число раз

Метасимвол '*' позволяет находить символы, встречающиеся несколько раз подряд, но число повторений при этом не определяется. Если же необходимо в процессе поиска учитывать точное количество последовательных вхождений символа в строку, следует применить шаблон \{ \}. Существует четыре варианта этого шаблона:

шаблон\{n\} Соответствует шаблону, встречающемуся ровно n раз подряд
шаблон\(n,\} Соответствует шаблону, встречающемуся не менее n раз подряд
шаблон\{,m\} Соответствует шаблону, встречающемуся не более m раз подряд
шаблон\{n, m\} Соответствует шаблону, встречающемуся не менее n и не более m раз подряд, где n и m — целые числа из интервала от 0 до 255

Представленный ниже шаблон соответствует последовательности из двух букв 'А', за которыми следует буква 'В':

А\{2\}В

В результате получим "ААВ".

В следующем шаблоне задано, что буква 'А' встречается не менее четырех раз подряд:

А\{4,\)В

Возможные результаты поиска — "ААААВ" или "АААААААВ", но не "АААВ". Поиск последовательности, в которой буква 'А' встречается от двух до четырех раз, выполняется по такому шаблону:

А\{2,4\}В

Будут найдены строки "ААВ", "АААВ", "ААААВ", но не "АВ" или "АААААВ". Вернемся к уже рассматривавшемуся примеру фильтрации текстового файла, фрагмент которого представлен ниже:

1234ХС9088 4523XX90D1 0011ХА9912 9931ХС3445

Допустим, требуется найти строки, в которых первые четыре символа — цифры, за ними идут символы "XX", а затем — еще четыре цифры. Решить данную задачу позволит такой шаблон:

[0-9]\{4\}ХХ[0-9]\(4\}

Применив этот шаблон к приведенному выше фрагменту, получим:

1234ХС9088 - не соответствует

4523XX900i - соответствует

0Q11XA9912 - не соответствует

9931ХС3445 - не соответствует

 

7.8. Примеры

В табл. 7.2 приведен ряд дополнительных примеров использования регулярных выражений.

Таблица 7.2. Полезные регулярные выражения

^the Соответствует строкам, которые начинаются символами "the"
[Ss]igna[lL] Соответствует словам "signal", "signaL", "Signal" и "SignaL"
[Ss]igna[lL]\. То же, что и в предыдущем случае, но слово, к тому же, должно завершаться точкой
tty$ Соответствует строкам, которые завершаются символами "tty"
^USER$ Соответствует слову "USER", которое является единственным в строке
\. Соответствует точке
^d..x..x..x Соответствует каталогам с установленным правом на выполнение для владельца, группы и других пользователей
^[^l] Исключает из списка файлов записи, соответствующие символическим ссыпкам
00* Находит строки, содержащие два или больше нулей подряд
[lL] Соответствует прописной и строчной букве 'l'
[iInN] Соответствует прописным и строчным буквам 'i' и 'n'
^S Соответствует пустой строке
^.*$ Соответствует строке, состоящей из любого числа символов
^……$ Соответствует строке, состоящей из шести символов
[a‑zA‑Z] Соответствует любой прописной или строчной букве
[a‑z][a‑z]* Соответствует по крайней мере одной строчной букве
[^0-9\$] При рассмотрении цифры и знаки доллара не учитываются
[^0-9A‑Za‑z] При рассмотрении не учитываются буквы и цифры
[123] Соответствует цифрам 1, 2 и 3
[Dd]evice Соответствует словам "Device" и "device"
De..ce Соответствует слову, в котором первые два символа — "De", за ними идут любые два символа, а затем -cимволы "се"
\^q Соответствует символам "^q"
^.$ Соответствует строке, содержащей только один символ
^\.[0-9][0-9] Соответствует строке, которая начинается с точки и двух цифр
"Device" Соответствует слову "Device"
De[Vv]ice\. Соответствует слову "DeVice" или "Device", после которого стоит точка
[0-9]\{2\}-[0-9]\ {2\}-[0-9]\{4\} Соответствует шаблону даты в формате dd‑mm‑yyyy
[0-9]\{3\}\.[0-9]\{3\}\ .[0-9]\{3\}\.[0-9]\{3\} Соответствует шаблону IP–адреса в формате ппп. ппп. ппп. ппп

 

7.9. Заключение

Регулярные выражения и методы работы с ними — важный аспект shell–программирования. Знакомство этой с методикой позволит повысить качество создаваемых сценариев, так как во многих случаях три–четыре команды фильтрации текста можно заменить одной командой с регулярным выражением.

В следующих главах мы рассмотрим примеры применения регулярных выражений в программах grep, sed и awk.

 

ГЛАВА 8

 

Семейство команд grep

Команда grep (global regular expression print — печать глобальных регулярных выражений) является наиболее известным инструментальным средством в UNIX и Linux. Она выполняет в текстовых файлах или стандартном входном потоке поиск выражений, соответствующих шаблону, с последующим отображением результата на экране. Команда grep может работать как с базовыми, так и с расширенными регулярными выражениями. Существует три разновидности этой команды:

   • grep -cтандартный вариант, которому уделено основное внимание в данной главе.

   • egrep -pаботает с расширенными регулярными выражениями (не поддерживает только оператор \ { \}).

   • fgrep — быстрый вариант команды grep. Вместо поиска выражений, соответствующих шаблону, выполняет поиск фиксированных строк из указанного списка. Пусть вас не вводит в заблуждение слово "быстрый". На самом деле это наиболее медленная из команд семейства grep.

Конечно, хотелось бы, чтобы существовала только одна, универсальная, команда

grep, и с этой ролью, в принципе, справляется GNU–версия grep. К сожалению,

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

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

   • параметры команды grep;

   • применение регулярных выражений в команде grep;

   • особенности поиска алфавитно–цифровых символов.

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

Прежде чем вы приступите к прочтению главы, создайте представленный ниже файл с именем data.f, содержащий информацию о заказах товаров. Структура записей этого файла такова:

1–й столбец — код города;

2–й столбец — код месяца, когда был сделан заказ;

3–й столбец — код заказа, включающий год, когда он был сделан;

4–й столбец — код товара;

5–й столбец — цена за единицу товара;

6–й столбец — код фирмы;

7–й столбец — количество заказанного товара.

$ cat : data .f

48 dec 3ВС1997 LPSX 68.00 LVX2A 138

483 sept 5AP1996 USP 65.00 LVX2C 189

47 oct 3ZL1998 LPSX 43.00 KVM9D 512

219 dec 2CC1999 CAD 23.00 PLV2C 68

484 nov 7PL1996 CAD 49.00 PLV2C 234

483 may 5PA1998 USP 37.00 KVM9D 644

216 sept 3ZL1998 USP 86.00 KVM9E 234

Разделителем полей является символ табуляции.

 

8.1. Команда grep

 

Общий формат команды grep таков:

grep [параметры] базовое_регулярное_выражение [файл]

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

 

8.1.1. Употребление кавычек

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

Если образец поиска состоит из какой‑нибудь системной переменной, например $PATH, рекомендуется тоже взять ее в двойные кавычки. Это связано с тем, что, прежде чем передавать аргументы команде grep, интерпретатор shell выполняет подстановку переменных, и команда grep получает значение переменной, которое может содержать пробелы. В этом случае будет выдано то же сообщение об ошибке, о котором говорилось в предыдущем абзаце.

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

 

8.1.2. Параметры команды grep

Ниже перечислены основные параметры команды grep:

-c Задает отображение только числового значения, указывающего, сколько строк соответствуют шаблону
-i Дает указание игнорировать регистр символов
-h Подавляет вывод имен файлов, включающих найденные строки (по умолчанию в выводе команды grep каждой строке предшествует имя файла, в котором она содержится)
-1 Задает отображение только имен файлов, содержащих найденные строки
-n Задает нумерацию выводимых строк
-s Подавляет вывод сообщений о несуществующих или нетекстовых файлах
-v Задает отображение строк, не соответствующих шаблону

 

8.1.3. Поиск среди нескольких файлов

Если в текущем каталоге требуется найти последовательность символов "sort" во всех файлах с расширением doc, выполните такую команду:

$ grep sort *.doc

Следующая команда осуществляет поиск фразы "sort it" во всех файлах текущего каталога:

$ grep "sort it" *

 

8.1 4. Определение числа строк, в которых найдено совпадение

Опция -c позволяет узнать, сколько строк соответствуют заданному шаблону. Это может оказаться полезным в том случае, когда команда grep находит слишком много строк, и их лучше вывести в файл, а не на экран. Рассмотрим пример:

$ grep -с "48" data.f

4

Команда grep возвращает число 4. Это означает, что в файле data.f обнаружены 4 строки, содержащие последовательность символов "48". Следующая команда отображает эти строки:

$ grep "48" data.f

48 dec 3BC1997 LPSX 68.00 LVX2A 138

453 sept 5AP1996 USP 65.00 LVX2C 189

484 nov 7PL1996 CAD 49.00 PLV2C 234

4 83 may 5PA1998 USP 37.00 KVM9D 644

 

8.1.5. Вывод номеров строк

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

$ grep -n "48" data.f

1: :48 dec 3BC1997 LPSX 68.00 LVX2A 138

2: :483 sept. 5AP1996 USP 65.00 LVX2C 189

5: :484 nov 7PL1996 CAD 49.00 PLV2C 234

6: :483 may 5PA1998 USP 37.00 KVM9D 644

Номера строк отображаются в первом столбце.

 

8.1.6. Поиск строк, не соответствующих шаблону

Благодаря опции -v можно отобрать те строки, которые не соответствуют шаблону. Следующая команда извлекает из файла data.f строки, не содержащие последовательность символов "48":

$ grep -v "48" data.f

47 oct 3ZL1998 LPSX 43.00 KVM9D 512

219 dec 2CC1999 CAD 23.00 PLV2C 68

216 sept 3ZL1998 USP 86.00 KVM9E 234

 

8.1.7. Поиск символов на границе слов

Вы, наверное, заметили, что при поиске строк, содержащих последовательность символов "48", были найдены строки заказов с кодом города не только 48, но также 483 и 484. Если необходимо найти заказ, у которого код города равен 48, добавьте в шаблон поиска символ табуляции:

$ grep "48" data.f

48 Dec 3BC1997 LPSX 68.00 LVX2A 138

Здесь запись означает нажатие клавиши [Tab].

Если не известно, какой пробельный символ является разделителем полей в файле, можно воспользоваться регулярным выражением \ > — признаком конца слова:

$ grep '48\>' data.f

48 Dec ЗВС1997 LPSX 68.00 LVX2A 138

 

8.1.8. Игнорирование регистра символов

По умолчанию команда grep чувствительна к изменению регистра символов. Чтобы провести поиск без учета регистра, воспользуйтесь опцией -i. В файле data.f обозначение месяца Sept встречается как в верхнем, так и в нижнем регистре. Поэтому для отбора строк обоих видов следует применить такую команду:

$ grep -i "sept" data.f

483 Sept 5AP1996 USP 65.00 LVX2C 189

216 sept 3ZL1998 USP 86.00 KVM9E 234

 

8.2. Команда grep и регулярные выражения

 

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

 

8.2.1. Выбор символов из списка

В предыдущей главе мы уже отмечали, что с помощью оператора [ 3 можно задать диапазон или список символов, включаемых в шаблон поиска. Предположим, требуется извлечь из файла data.f строка заказов, сделанных в городах, код которых равен 483 или 484. Поставленную задачу решает следующая команда:

$ grep '48[34]' data.f

493 Sept 5AP1996 USP 65.00 LVX2C 189

484 nov 7PL1996 CAD 49.00 PLV2C 234

483 may 5PA1998 asp 37.00 KVM9D 644

 

8.2.2. Инверсия шаблона с помощью метасимвола ""

Следующая команда находит в файле data.f строки, не начинающиеся с цифры 4 или 8:

$ grep '^[^48]' data.f

219 dec 2СС1999 CAD 23.00 PLV2C 68

216 sept 321Л998 USP 86.00 KVM9E 234

Символ '^' заключенный в квадратные скобки, говорит о том, что шаблон соответствует любому символу, кроме цифр 4 и 8. Символ 'Л' в начале шаблона — это признак того, что поиск производится с начала каждой строки.

 

8.2.3. Шаблон, соответствующий любому символу

Предположим, в файле data.f требуется найти коды фирм, которые начинаются на букву 'К' и заканчиваются буквой 'D'. Реализуется это следующим образом:

$ grep 'K…D' data.f

47 Oct 3ZL1998 LPSX 43.00 KVM9D 512

483 may 5PA199S USP 37.00 KVW9D 644

Данный шаблон рассчитан на то, что коды фирм в файле состоят из пяти символов.

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

$ grep '[A‑Z][A‑Z]..C' data.f

483 Sept 5AP1996 USP 65.00 LVX2C 189

219 dec 2CC1999 CAD 23.00 PLV2C 68

484 nov 7PL1996 CAD 49.00 PLV2C 234

 

8.2.4. Поиск по дате

Представленная ниже команда находит все заказы, которые были сделаны в 1996 или 1998 году и коды которых начинаются с цифры 5:

$ grep '5..199[68]' data.f

483 Sept 5АР1996 USP 65.00 LVX2C 189

483 may 5РА1998 USP 37.00 KVM9D 644

Структура используемого здесь шаблона такова: первым символом является цифра 5, за ней следует два произвольных символа, затем число 199, а последним символом может быть либо цифра 6, либо цифра 8.

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

$ grep '[0-9]\{3\}8' data.f

47 Oct 3ZL1998 LPSX 43.00 KVM9D 512

483 may 5PA1998 USP 37,00 KVM9D 644

216 sept 3ZL1998 USP 86.00 KVM9E 234

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

 

8.2.5. Комбинированные диапазоны

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

$ grep ' [0-9][0-5][0-63' data.f

47 Oct 3ZL1998 LPSX 43.00 KVM9D 512

484 nov 7PL1996 CAD 49.00 PLV2C 234

483 may 5PA199B USP 37.00 KVM9D 644

216 sept 3ZL1998 USP 86.00 KVM9E 234

Как видите, отображается больше информации, чем нам необходимо. Значит, в. шаблоне поиска недостаточно уточнен критерий отбора информации. Очевидно, следует указать, что поиск нужно начинать в начале строки. Для этого применим метасимвол'^':

$ grep '^[0-9][0-5][0-6]' data.f

216 sept 3ZL1998 USP 86.00 KVM9E 234

 

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

Если необходимо найти какую‑либо строку, которая содержит цифру 4, повторенную минимум дважды, задайте такую команду:

$ grep '4\{2,\}' data.f

483 may 5PA1998 USP 37.00 KVM9D 644

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

$ grep '9\{3,\}' data.f

219 dec 2СС1999 CAD 23.00 PLV2C 68

Иногда точное число повторений символа не известно. В таких случаях окажется полезной команда со следующей структурой:

$ grep '8\{2,6\}3' myfile

83 — не соответствует

888883 -cоответствует

8884 — не соответствует

88883 -cоответствует

Здесь задан поиск строк, в которых цифра 8 встречается от двух до шести раз подряд и предшествует цифре 3.

 

8.2.7. Выбор из нескольких шаблонов

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

$ grep -E '219|216' data.f

219 dec 2СС1999 CAD 23.00 PLV2C 68

216 sept 3ZL1998 USP 86.00 KVM9E 234

 

8.2.8. Поиск пустых строк

Для поиска в файле пустых строк можно составить шаблон из метасимволов '^' и '$':

$ grep '^$' myfile

 

8.2.9. Поиск специальных символов

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

$ grep '\.' myfile

Следующая команда ищет в файле myfile двойные кавычки:

$ grep "\" ' myfile

А вот эта команда отбирает в листинге команды ls -l запись, соответствующую файлу control.conf.

$ ls -l | grep 'control\.conf'

 

8.2.10. Поиск имен файлов, соответствующих заданному формату

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

$ grep '[а–z]\{1,6\}\.[А–Z]\{1,2\}' filename.deposit

yrend. AS - соответствует

mothdf — не соответствует

sca.PP - соответствует

qp.RR - соответствует

 

8.2.11. Поиск IP–адресов

Администратору DNS–сервера приходится поддерживать большое количество IP–адресов, относящихся к различным сетям. В моей системе файл ipfile может содержать свыше 200 адресов. Мне часто приходится выполнять поиск всех адресов в формате "nnn.nnn" (т. е. адресов, содержащих две трехзначные последовательности, оканчивающиеся точкой). Для этой дели я пользуюсь следующей командой:

S grep '[0-9]\{3\}\.[0-9]\{3\}\.' ipfile

 

8.2.12. Поиск строк с использованием подстановочных знаков

Предположим, имеется такой файл:

$ cat testfile

looks likes looker long

Следующая команда находит в нем слова, начинающаяся с буквы 'l', после которой идет произвольное число символов, а за ними — буква 's':

$ grep 'l.*s' testfile

looks likes

Показанная ниже команда отбирает слова, начинающиеся с буквы 'l', после которой идет произвольное число символов, затем — буква 'к', а после нее — еще один символ:

$ grep 'l.*k.' testfile

looks likes

Следующая команда находит слова, в которых буква о' встречается не менее двух раз подряд:

$ grep 'ооо*' testfile

looks

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

$ grep 'device$' *

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

 

8.3. Классы символов

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

Таблица 8.1. Основные классы символов и эквивалентные им регулярные выражения

Класс Эквивалентное регулярное выражение
[:upper:] [A‑Z]
[:lower:] [a‑z]
[:digit:] [0-9]
[:alnum:] [0-9a‑zA‑Z]
[:space:] символы пробела
[:alpha:] [a‑zA‑Z]

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

Рассмотрим несколько примеров на базе нашего файла data.f. Предположим, требуется найти все коды заказов, которые содержат цифру 5, сопровождаемую по крайней мере двумя буквами в верхнем регистре:

$ grep '5[[:upper:]][[:upper]] ' data.f

483 Sept 5АР1996 USP 65.00 LVX2C 189

483 may 5РА1998 USP 37.00 KVM9D 644

Вот как осуществляется поиск всех кодов товара, которые оканчиваются буквой 'P' или 'D':

$ grep '[[:upper:]][[:upper:]][PD]' data.f

483 Sept 5AP1996 USP 65.00 LVX2C 159

219 dec 2CC1999 CAD 23.00 PLV2C 68

484 nov 7PL1996 CAD 49.00 PLV2C 234

483 may 5PA1998 USP 37.00 KVM9D 644

216 sept 3ZL1998 USP 86.00 KVM9E 234

 

8.4. Дополнительные примеры использования команды grep

 

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

 

8.4.1. Фильтрация списка файлов

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

$ 1s -l | grep '^d'

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

S ls -l | grep '^[d]'

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

$ ls -1 | grep '^d.....x..x'

 

8.4.2. Подавление вывода сообщений об ошибках

Допустим, вы хотите найти запись пользователя louise в системном файле паролей:

$ grep louise /etc/passwd

louise:lxAL6GW9G.ZyY:501:501:Accounts Sect1С:/home/accts/louise:/bin/sh

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

$ grep louise /etc/password

grep: /etc/password: No such file or directory

Команда grep выводит сообщение об ошибке, в которой говорится о том, что указанного файла не существует. Можно попробовать провести поиск во всех файлах каталога /etc:

$ grep louise /etc/*

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

В подобной ситуации можно воспользоваться опцией -s, которая подавляет вывод сообщений об ошибках:

$ grep -a louise /etc/*

Если ваша версия команды grep не поддерживает данную опцию, воспользуйтесь следующей командой:

$ grep louise /etc/* 2> /dev/null

Эта команда направляет поток ошибок (2>) в системную корзину (устройство /dev/null). На жаргоне системных администраторов это устройство называется битодробилкой.

 

8.4.3. Фильтрация списка процессов

Совместное применение команд grep и ps а позволяет выяснить, выполняется ли в системе некоторый процесс. Опция а команды ps задает вывод списка всех процессов, включая процессы других пользователей. Например, следующая команда проверяет, выполняется ли в данный момент процесс named:

$ ps а | grep named

211 ? S 4.56 named

303 3 s 0.00 grep named

Выводимый результат включает также саму команду grep, поскольку она создает процесс, выполняющий фильтрацию текста в канале, и команда ps распознает этот процесс. Чтобы исключить команду grep из результата, задайте дополнительную команду grep с опцией -v:

$ ps ах | grep named | grep -v "grep"

211 ? S 4.56 named

 

8.5. Команда egrep

Команда egrep (extended grep( воспринимает как базовые, так и расширенные регулярные выражения. Одной из привлекательных ее особенностей является возможность сохранения шаблонов поиска в файле. Подключается этот файл с помощью опции -f. Рассмотрим пример:

$ cat grepstrings

484

47

$ egrep -f grepstrings data.f

В этом случае в файле data.f осуществляется поиск записей, которые содержат последовательность символов "484" или "47". Создание файла шаблонов удобно в том случае, когда шаблонов очень много и вводить их в командной строке затруднительно. Если шаблонов немного, воспользуйтесь метасимволом '|', который позволяет сделать выбор между несколькими шаблонами. Например, следующая команда ищет в файле data.f записи, содержащие последовательность символов "3ZL" или "2СС":

$ egrep '3ZL|2CC' data.f

47 Oct 3ZL1998 LPSX 43 .00 KVM9D Ы2

219 dec 2СС1999 CAD 23 .00 PLV2C 68

216 sept 3ZL1998 USP 86 .00 KVM9E 234

Метасимвол '|' можно применять более одного раза. Например, чтобы узнать, зарегистрированы ли в системе пользователи louise, matty и pauline, выполните команду who и направьте результаты ее работы команде egrep:

$ who | egrep 'louise|matty|pauline'

louise pty8

mattу tty02

pauline pty2

Круглые скобки позволяют представить выражение с несколькими шаблонами как один шаблон. Так, с помощью представленной ниже команды можно найти в текущем каталоге файлы, в которых встречаются слова из ряда "shutdown", "shutdowns", "reboot" и "reboots":

$ egrep '[shutdown|reboot]s?' *

 

8.6. Заключение

В настоящей главе продемонстрированы лишь некоторые из многочисленных возможностей команды grep, которая является универсальным инструментом фильтрации, очень популярным среди пользователей UNIX и Linux. Существует несколько ее разновидностей, которые в некоторых системах заменены единой GNU–командой grep. Как будет показано далее в этой книге, команда grep также является важным инструментом shell–программирования, используемым в связке с другими утилитами UNIX.

 

ГЛАВА 9

 

Утилита awк

При форматировании отчетов и извлечении информации из больших текстовых файлов неоценимую помощь оказывает утилита awk, которая обладает мощными средствами обработки текста. Как показывает опыт, среди всех инструментов фильтрации, имеющихся в интерпретаторе shell, труднее всего освоить работу именно с awk. Причина этого явления не ясна. Может быть, дело в синтаксисе утилиты или не совсем понятных сообщениях об ошибках, таких как bailing out и awk: cmd. line:. Подобные сообщения довольно часто встречаются при программировании на языке awk. Именно так — язык awk, поскольку это совершенно самостоятельный язык программирования. Возможно, изучать его нелегко, но его совместное применение с другими инструментальными средствами, такими как команда grep и редактор sed, позволяет значительно упростить программирование в интерпретаторе shell.

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

   • выборка текстовых полей;

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

   • сравнение текстовых полей;

   • передача параметров утилите awk;

   • базовые команды и сценарии awk.

Название утилиты awk составлено из начальных букв фамилий разработчиков языка: Ахо (Aho), Вайнбергера (Weinberger) и Кернигана (Kernighan). Существуют также утилиты nawk и gawk, обладающие усовершенствованными возможностями обработки текста. Но эти утилиты здесь не рассматриваются.

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

 

9.1. Вызов awk

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

awk [-F разделитель_полей] 'сценарий' входной_файл…

В одинарных кавычках указывается список инструкций языка awk. Именно этому способу отдается преимущество в примерах настоящей главы.

Задавать разделитель полей с помощью опции -F не обязательно, так как по умолчанию утилита awk использует для этих целей пробел. Но, например, в файле /etc/passwd поля отделяются друг от друга двоеточием. В данном случае вызов утилиты выглядит так:

awk -F: 'сценарий' входной_файл…

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

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

awk -f файл_сценария входной_файл…

Опция -f свидетельствует о том, что инструкции awk содержатся в файле сценария.

Утилита awk анализирует информацию, содержащуюся в одном или нескольких входных файлах.

 

9.2. Сценарии

 

Сценарий awk — это набор инструкций, состоящих из шаблонов и связанных с ними процедур. Когда утилита просматривает записи входного файла, она проверяет, установлена ли опция -F или переменная FS (о ней мы поговорим ниже), задающие разделители полей записи. По умолчанию в качестве разделителя принят пробел. При обнаружении символа новой строки прочитанная строка классифицируется как запись, и к ней применяются инструкции сценария. Процесс чтения записей продолжается до тех пор, пока не будет обнаружен признак конца файла.

В табл. 9.1 приведен образец входного файла и продемонстрировано, как утилита awk его анализирует. Утилита последовательно просматривает строки файла. Отыскав первый символ–разделитель, она помечает все предыдущие символы как поле номер L Символы между первым и вторым разделителями обозначаются как поле номер 2 и т. д. Процесс анализа завершается при обнаружении символа новой строки, который по умолчанию считается признаком конца записи. После этого содержимое полей сбрасывается и утилита переходит к следующей строке файла.

Таблица 9.1. Образец анализа входного файла

Поле 1 Разделитель Поле 2 Разделитель Поле 3 Разделитель Поле 4 и символ новой строки.
P. Bunny (запись 1) # 02/99 # 48 # Yellow\n
J. Troll (запись 2) # 07/99 # 4842 # Brovm-3\n

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

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

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

 

9.2.2. Работа с полями и записями

На поля текущей записи можно ссылаться следующим образом: $1, $2… $п. Этот метод называется идентификацией полей. Подобная схема обозначений значительно облегчает работу с полями. Например, в качестве ссылки на первое и третье поля достаточно указать:

$1, $3

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

$1, $2, $3, $4, $5

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

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

Чтобы вывести на экран содержимое записи, задайте в теле процедуры команду print со списком нужных полей.

Тестовый файл

Прежде чем мы перейдем к практической части, создайте файл grade.txt, на котором основано большинство примеров настоящей главы. Файл этот должен содержать несколько записей из локальной базы данных секции каратистов.

$ cat grade.txt

M. Tansley 05/99 48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

Назначение полей таково:

1 — имя;

2 — дата получения пояса;

3 — порядковый номер ученика;

4 — полученный пояс;

5 — возраст;

6 — текущий рейтинг;

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

Поля разделяются пробелами, поэтому при вызове утилиты awk можно не указывать опцию -F.

Сохранение выходных данных

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

$ awk '{print $0}' grade.txt > grade.out

При этом выходные данные не будут отображаться на экране.

Второй способ заключается в применении команды tee, о которой рассказывалось в главе 5. Выходные данные передаются в файл и одновременно отображаются на экране. Например:

$ awk '{print $0}' grade.txt | tee grade.out

Отображение всех записей

В приведенной ниже команде утилита awk просматривает файл grade.txt и, поскольку шаблон не указан, отображает содержимое всех записей:

$ awk '{print $0}' grade.txt

M. Tansley 05/99 48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
S. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

Отображение отдельных полей всех записей

Предположим, требуется отобразить на экране только имена спортсменов и названия поясов, которыми они обладают. Соответствующие данные хранятся в полях $1 и $4, поэтому введем такую команду:

$ awk '{print $1, $4}' grade.txt

M. Tansley Green

J. Lulu green S. Bunny Yellow J. Troll Brown-3

L. Tansley Brown-2

Отображение заголовка отчета

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

в отдельной строке благодаря Escape–последовательности \n.: Заголовок отчета формируется в процедурной части шаблона begin.

$ awk 'BEGIN {print "Name Belt\n -"} \

{print $1 " \t" $4}' grade.txt

Name Belt
M. Tansley Green
J. Lulu green
P. Bunny Yellow
J. Troll Brown-3
L. Tansley Brown-3

Отображение резюме отчета

Чтобы добавить в конец отчета строку "end‑of‑report", следует воспользоваться шаблоном end. Этот шаблон употребляется для обозначения действий, которые выполняются после обработки последней записи входного файла.

$ awk 'BEGIN {print "Name\n "} {print $1} \

END {print "\nend‑of‑report"}' grade.txt

Name

M. Tansley

J. Lulu

P. Bunny , .

J. Troll L. Tansley

end‑of‑report

Обработка сообщений об ошибках

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

Давайте смоделируем ситуацию, при которой возникает синтаксическая ошибка, например, пропустим двойную кавычку в предыдущей команде:

$ awk 'BEGIN {print "Name\n "} {print $1} \

END {print "\nend‑of‑report}' grade.txt

awk: cmd. line:2: END {print "\nend‑of‑report}

awk: end. line:2: ^ unterminated string

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

   • убедитесь, что сценарий awk целиком заключен в одинарные кавычки;

   • удостоверьтесь, что все кавычки внутри сценария являются парными;

   • проверьте, заключены ли процедуры в фигурные скобки, а условные конструкции - в круглые скобки.

Более понятным является сообщение об ошибке, возникающей при создании ссылки на несуществующий файл:

$ awk 'END {print "End‑of‑report"}' grades.txt

awk: cmd. line":2: fatal: cannot open file 'grades.txt' for reading (No such file or directory)

Ввод данных с клавиатуры

Давайте посмотрим, что произойдет, если не указать файл grade.txt в командной строке:

$ awk 'BEGIN {print "Name Belt\n "} \

{print $1" \t"$4}'

Name Belt

>

С помощью шаблона begin на экран выводится заголовок отчета, при этом сам отчет пуст, а утилита awk ожидает получения входных данных с клавиатуры (об этом свидетельствует строка приглашения >). Вы должны ввести их вручную. После нажатия клавиши [Enter] введенная строка интерпретируется как входная запись и по отношению к ней выполняются соответствующие инструкции. По завершении ввода данных нажмите [Ctrl+D]. Подобный метод работы применяется довольно редко, поскольку чреват большим количеством опечаток и ошибок.

 

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

При изучении возможностей команды grep приводилось большое количество примеров регулярных выражений, поэтому мы не будем еще раз останавливаться не описании их синтаксиса. Ниже, когда будут рассматриваться операторы, вы встретите много примеров команд awk с регулярными выражениями.

В сценарии awk регулярное выражение выделяется с обеих сторон символами косой черты: /регулярное_выражение/. Например, если в текстовом файле нужно найти строку, содержащую слово "Green", следует задать шаблон /Green/.

 

9.2.4. Метасимволы

Перечисленные ниже метасимволы могут встречаться в регулярных выражениях утилиты awk:

\ ^ $ . [ ] | ( ) * + ?

Следует остановиться на описании двух метасимволов, которые не рассматривались в главе 7, поскольку они специфичны для awk и не применяются в команде grep и редакторе sed.

+ Указывает на то, что предыдущий символ встречается один или несколько раз. Например, выражение /t+/ соответствует одной или нескольким буквам 't', а выражение /[а–z]+/ — любой последовательности строчных букв.

? Указывает на то, что предыдущий символ встречается не более одного раза. Например, выражение /xy?z/ соответствует строкам "XYZ" и "XZ".

 

9.2.5. Операторы

В awk существует достаточно много операторов, манипулирующих числами, строками, переменными, полями и элементами массива. Ниже приведен список основных операторов.

=, += *= /= %= Операторы присваивания (простого и составного)
? ; Условный оператор
|| && ! Логические операторы ИЛИ, И, НЕ
~ !~ Операторы сравнения с регулярным выражением (совпадение, несовпадение(
< <= == != > >= Операторы простого сравнения
+ - * / % Арифметические операторы (сложение, вычитание, умножение, деление, деление по модулю)
++ -- Инкремент и декремент (могут быть префиксными и пост-

 

9.2.6. Операторы сравнения

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

Таблица 9.2. Операторы сравнения утилиты awk

Оператор Проверка
< Меньше
<= Меньше или равно
== Равно
! = Не равно
> Больше
>= Больше или равно
~ Соответствие регулярному выражению (фрагмент строки совпадает с шаблоном)
! ~ Несоответствие регулярному выражению (в строке не обнаружено совпадений с шаблоном)

Проверка на совпадение

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

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

$ awk '{if($3 ~ /Brown/) print $0}' grade.txt

J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

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

$ awk '$0 ~ /Brown/' grade.txt

J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

Приведенная команда означает следующее: если в строке встречается слово "Brown", вывести ее на экран.

Проверка на равенство

Допустим, необходимо получить информацию об ученике с номером 48. Показанная ниже команда не позволит решить данную задачу, поскольку в файле есть множество номеров, содержащих последовательность цифр "48":

$ awk '{if($3 ~ /48/) print $0}' grade.txt

М. Tansley 05/99 "48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26

Чтобы найти точное совпадение, воспользуйтесь оператором ==:

$ awk '$3 == "48"' grade.txt

P. Bunny 02/99 48 Yellow 12 35 28

Проверки на несовпадение и неравенство

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

$ awk '$0 !~ /Brown/' grade.txt

M. Tansley 05/99 48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28

He забывайте, что по умолчанию утилита awk выводит на экран все записи, отвечающие указанному критерию отбора, поэтому нет необходимости задавать какое‑либо действие. Если вместо предыдущей команды указать

$ awk '$4 != "Brown"' grade.txt

получим ошибочный результат. Эта команда означает, что требуется найти строки, в которых четвертое поле не равно "Brown". Такому критерию удовлетворяют все строки в файле. Конечно, если нужно найти обладателей поясов, отличных от "Brown-2", можно применить следующую команду:

$ awk '$4 != "Brown-2"' grade.txt

M. Tansley 05/99 48311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28
S. Trnll 07/99 4842 Brown-3 12 26 26

Обратите внимание на один важный момент: строка "Brown-2" заключена в двойные кавычки. Если этого не сделать, четвертое поле будет сравниваться с содержимым переменной Brown-2. Вряд ли в вашем интерпретаторе существует такая переменная, поэтому вместо нее будет подставлена пустая строга, и вы получите совершенно другой результат.

Проверка "меньше чем"

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

$ awk '{if($6 < $7) print $1 " — try better at the next competition"}' grade.txt

M. Tansley — try better at the next competition J. Lulu — try better at the next competition

Проверка "меньше или равно"

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

$ awk '{if($6 <= $7) print $1}' grade.txt

M. Tansley

J. Lulu J. Troll

Проверка "больше чем"

Следующая команда формирует список лидеров соревнования:

$ awk '{if ($6 > $7) print $1}' grade.txt

L. Tansley P. Bunny

 

9.2.7. Логические операторы

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

&& И: чтобы результат был истинным, оба операнда должны быть истинными | | ИЛИ: чтобы результат был истинным, хотя бы один из операндов должен

быть истинным! НЕ: результат проверки инвертируется

Оператор логического И

Предположим, перед нами поставлена задача выяснить, кому был присвоен зеленый пояс в мае 1999 года. Строки, отвечающие этому условию, должны в первом поле содержать значение "05/99", а во втором — "Green":

$ awk '{if($2 == "05/99" && $4 == "Green") print $0}' grade.txt

M. Tansley 05/99 48311 Green 8 40 44

Оператор логического ИЛИ

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

$ awk '{if ($4 == "Yellow" ) || $4 ~ /Brown/} print $0}' grade.txt

P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 , 12 30 28

Приведенную команду можно упростить с помощью метасимвола, означающего выбор любого из двух шаблонов:

$ awk '$4 ~ /Yellow|Brown/' grade.txt

P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown-2 12 30 28

 

9.2.8. Операторы присваивания и арифметические операторы

С помощью операторов присваивания и арифметических операторов можно создавать в сценариях awk локальные переменные и манипулировать их значениями.

Создание локальных переменных

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

имя_переменной= $n

где n -cуществующий номер поля.

В следующем примере мы создадим две переменные: name, содержащую имена учеников (поле 1), и belts, содержащую названия поясов (поле 4). Затем будет произведен поиск учеников, обладающих желтым поясом.

$ awk '(name=$1; belts=$4; if(belts ~ /Yellow/) print name" is belt "belts}' grade.txt

P.Bunny is belt Yellow

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

Проверка значения поля

В следующем примере мы проверим, кто из учеников набрал в соревнованиях менее 27 очков. В первом варианте команды значение поля $6 непосредственно сравнивается с числом 27:

$ awk '$6 < 27' grade.txt

J.Lulu 06/99 48317 green 9 24 26
J.Troll 07/99 4842 Brown-3 12 26 26

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

потребуется. Конечно, этот прием неэффективен в случае одноразовых команд, зато очень полезен в больших сценариях, так как позволяет легко вносить в них изменения. Во втором примере мы создадим переменную baseline и присвоим ей значение 27, а затем сравним с ней интересующее нас поле $6:

$ awk 'BEGIN {BASELINE=27} {if ($6 < BASELINE) print $0}' grade.txt

J.Lulu 06/99 48317 green 9 24 26

J.Troll 07/99 4842 Brown 3 12 26 26

Изменение значения числового поля

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

В следующем примере на экран выводятся имена учеников и их рейтинговые очки, а рейтинг ученика по имени M. Tansley уменьшается на единицу:

$ awk '{if($1="M. Tansley") $6=$6-1; print $1, $6}' grade.txt

M.Tansley 39

J.Lulu 24

P.Bunny 35

J.Troll 26

L.Tansley 30

Изменение значения текстового поля

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

$ awk '{if($1="J. Troll") $1="J. L.Troll"; print $1}' grade.txt

M.Tansley

J.Lulu

P.Bunny

J.L.Troll

L.Tansley

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

Отображение только измененных записей

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

$ awk '{if($1=="J. Troll") {$1="J. L.Troll"; print $1)}' grade.txt

J.L.Troll

Создание нового поля

Аналогично локальным переменным в сценарии awk можно создавать новые поля. Например, мы можем создать поле $8, содержащее разницу полей $6 и $7 в том случае, если значение в поле $7 больше, чем в поле $6:

$ awk 'BEGIN {print "Name\t\tDifference"} {if($6 < $7) \ {$8=$7-$6; print $1" \t"$8}}' grade.txt

Name Difference

L.Tansley 4

J.Lulu 2

Суммирование столбцов

Для вычисления суммарного рейтинга учеников секции мы создадим переменную tot и с помощью выражения tot+=$6 будем прибавлять к ней значение поля $6 при обработке каждой записи. По завершении обработки записей в процедурной части шаблона end итоговое значение переменной tot будет выведено на экран.

$ awk 'tot+=$6; END {print "Club student total points: " tot}' grade.txt

M.Tansley 05/99 48311 Green 8 40 44
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
L.Tansley 05/99 4712 Brown-2 12 30 28

Club student total points: 155

Вероятно, вы заметили, что утилите awk не было дано указание выводить на экран все записи -oна сделала это сама. Причина такого поведения заключается в том, что выражение tot+=$6 относится к шаблонной части инструкции и не задает критерия отбора строк, т.е. применяется ко всем записям. А поскольку процедурная часть этого шаблона отсутствует, выполняется действие по умолчанию — команда print SO.

Если файл велик, можно не выводить на экран все записи, а лишь отобразить итог. Для этого достаточно взять выражение tot+=$6 в фигурные скобки, чтобы перенести его в процедурную часть инструкции:

$ awk '{tot+=$6}; END {print "Club student total points: " tot}' grade.txt

Club student total points: 155

Суммирование размеров файлов

При просмотре содержимого каталога часто требуется узнать общий размер всех файлов в нем, исключая файлы в подкаталогах и скрытые файлы. Алгоритм решения этой задачи таков: результаты работы команды ls -l (формирует список файлов с расширенной информацией о них; см. главу 1) направляются утилите awk, которая удаляет записи, начинающиеся с символа 'd' (признак каталога), и вычисляет сумму по 5–му столбцу (содержит размер файла).

Представленная ниже команда отображает список файлов текущего каталога (имя файла берется из 9–го столбца), указывая размер каждого из них, а в конце выводит суммарный размер файлов, накопленный в переменной tot:

$ ls -l | awk '/^[^d]/ {print $9"\t"$5; tot+=$5} END {print "total KB: "tot}' dev_pkg.fail 345 failedlogin 12416

messages 4260
зи1од 12810
utap 1856
wtap 7104
total KB: 38791

Если необходимо включить в список скрытые файлы, следует вместо команду ls -l задать команду 1s -la.

 

9.2.9. Встроенные переменные

Утилита awk имеет ряд встроенных переменных, которые позволяют получить подробную информацию о входном потоке и настройках awk. Значения некоторых переменных можно изменять. В табл. 9.3 кратко описаны основные переменные.

Таблица 9.3. Встроенные переменные awk

Переменная Что содержит
ARGC Количество аргументов в командной строке (поддерживается только: в nawk и gawk)
ARGV Массив аргументов командной строки (поддерживается только в nawk и gawk)
ENVIRON Массив переменных среды (поддерживается только в nawk и gawk)
FILENAME Имя файла, обрабатываемого в текущий момент
FNR Количество уже обработанных записей в текущем файле (поддерживается только в nawk и gawk)
FS Разделитель полей во входном потоке (по умолчанию пробел); аналогична опции -F командной строки
NF Количество полей в текущей записи
NR Количество обработанных записей во входном потоке
OFS Разделитель полей в выходном потоке (по умолчанию пробел)
ORS Разделитель записей в выходном потоке (по умолчанию символ новой строки)
RS Разделитель записей во входном потоке (по умолчанию символ
новой строки)

Переменная ARGC хранит число аргументов командной строки, переданной сценарию awk (точнее, nawk или gawk, т. к. эта переменная появилась только в новых версиях утилиты). Переменная argv хранит значения аргументов командной строки. Доступ к нужному аргументу осуществляется с помощью ссылки ARGV[n], где п — порядковый номер аргумента в командной строке.

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

ENVIRON["EDITOR"]=="vi"

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

Переменная fnr хранит номер записи, которую утилита awk обрабатывает в текущий момент; ее значение меньше или равно значению переменной NR, которая отслеживает общее число обработанных записей входного потока. Если сценарий получает доступ более чем к одному файлу, переменная FNR сбрасывается в ноль при открытии каждого нового файла. В переменную NF записывается количество полей текущей записи. Ее значение сбрасывается по достижении конца записи.

Переменная FS содержит символ, используемый в качестве разделителя полей входного потока. Эту переменную можно установить из командной строки с помощью опиии -F. По умолчанию разделителем полей служит пробел. Переменная ofs содержит символ, являющийся разделителем полей в выходном потоке. По умолчанию это тоже пробел.

В переменной ORS хранится разделитель записей в выходном потоке. По умолчанию им является символ новой строки (\n). Переменная RS содержит разделитель записей во входном потоке (в большинстве случаев это тоже символ \n).

Переменные NF, NR и FILENAME

Представленная ниже команда позволяет быстро определить число записей во входном файле grade.txt. Значение переменной NR отображается по завершении обработки файла.

$ awk 'END {print NR}' grade.txt

В следующем примере на экран выводятся все записи исходного файла. Каждой из них предшествуют два числа: количество полей в записи (переменная NF) и номер записи в файле (переменная nr). В самом конце отображается имя входного файла (переменная FILENAME).

$ awk '{print NF, NR, $0} END {print FILENAME}' grade.txt

1 M. Tansley 05/99 48311 Green 8 40 44
2 J. Lulu 06/99 48317 green 9 24 26
3 P. Bunny 02/99 48 Yellow 12 35 28
4 J. Troll 07/99 4842 Brown-3 12 26 26
5 L. Tansley 05/99, 4712 Brown-2 12 30 28

grade.txt

Переменную NF удобно использовать, когда требуется извлечь из путевого имени последнюю часть, т. е. имя файла или каталога. В этом случае необходимо указать, что разделителем полей является символ '/' и использовать ссылку $NF, которая является обозначением последнего поля текущей записи. Например:

$ pwd

/usr/local/etc

$ echo $PWD | awk -F/ '{print $NF}'

Переменная среды $PWD хранит путевое имя текущего каталога.

 

9.2.10. Встроенные функции работы со строками

Утилита awk располагает набором универсальных функций преобразования строк. В табл. 9.4 перечислены основные из них.

Таблица 9.4. Функции работы со строками

Функция Назначение
gsub(r,.s) Выполняет глобальную замену каждой строки, соответствующей регулярному выражению г, строкой s в пределах текущей записи; появилась в nawk
index(s, t) Возвращает позицию первого вхождения подстроки t в строку s
length(s) Возвращает длину строки s
match(s, r) Проверяет, содержит ли строка s подстроку, соответствующую, регулярному выражению r; появилась в nawk
split(s, a,fs) Разбивает строку s на элементы, разделенные символом fs, и помещает полученные элементы в массив а
sub(r, s) Выполняет замену самой первой строки, соответствующей регулярному выражению r, строкой s в пределах текущей записи; появилась в nawk
substr(s, p[,n]) Возвращает подстроку строки s, начинающуюся с позиции p и имеющую длину n; если аргумент п не задан, концом подстроки считается символ \0 (признак конца строки)

Функция gsub()

Благодаря функции gsub() вы сможете выполнить в текущей записи глобальную замену строк, соответствующих заданному регулярному выражению. Например, для изменения номера ученика с 4842 на 4899 введите такую команду:

$ awk 'gsub(4842,4899) {print $0}' grade.txt

J. Troll 07/99 4899 Brown-3 12 26 26

Функция index()

Чтобы узнать позицию первого вхождения подстроки t в строку s, воспользуйтесь функцией index (), только не забудьте взять ее аргументы в двойные кавычки, иначе они будут восприниматься как имена переменных среды. Например, следующая команда возвращает число, определяющее позицию подстроки "ny" в строке "Bunny":

$ awk 'BEGIN {print index("Bunny","ny")}' grade.txt

4

Функция length()

Функция length() возвращает длину переданного ей текстового аргумента. В показанном ниже примере производится поиск информации об ученике с номером 4842, а затем определяется длина имени ученика:

$ awk '$3=4842 {print length($1)" "$1}' grade.txt

7 J.Troll

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

$ awk 'BEGIN {print length("A FEW GOOD MEN")}'

14

Функция match()

Функция match() позволяет проверить, содержит ли строка заданную подстроку. Последняя может быть представлена как литералом в двойных кавычках, так и регулярным выражением. Если поиск прошел успешно, возвращается число, определяющее позицию, с которой начинается вхождение подстроки в искомую строку. В случае неудачи возвращается ноль. Следующая команда проверяет, содержит ли имя ученика с номером 48317 символ 'u':

$ awk '$3=48317 {print match ($1, "u"), $1} ' grade.txt

4 J. Lulu

Функция split()

Функция split() преобразует переданную ей строку в массив и возвращает число элементов в полученном массиве. В следующем примере заданная строка разбивается на три элемента, которые помещаются в массив myarray. Разделителем элементов в данном случае является символ '#'.

$ awk 'BEGIN {print split("123#456#678", myarray,"#"))'

3

Массив myarray будет иметь такую структуру:

mуarray[1]="123" myarray[2]="4 56"

myarray[3]="678"

Функция sub()

Функция sub() применяется для поиска строки, соответствующей заданному шаблону, и ее замены при первом появлении. В этом состоит отличие данной функции от функции gsub(), которая находит все случаи вхождения подстроки в строку, производя соответствующее число замен. Приведенная ниже команда находит запись ученика J. Troll и меняет его рейтинг с 26 на 29 (поле 6), при этом значение поля 7 (тоже 26) остается неизменным:

$ awk '$1=="J. Troll" {sub(26,29,$0) )' grade.txt

J. Troll 07/99 4842 Brown-3 12 29 26

Функция substr()

Функция substr() возвращает указанную часть строки. Вам нужно задать позицию, с которой начинается вхождение подстроки в искомую строку, и длину подстроки. Рассмотрим пример:

$ awk '$1=="L. Tansley" {print substr($1,1,5))' grade.txt

L. Tan

Эта команда возвращает из строки "L. Tansley" подстроку, начинающуюся с первого символа и занимающую пять символов.

Если значение третьего аргумента значительно превышает реальную длину строки, функция substr () возвращает все символы строки, начиная с указанной позиции:

$ awk '$1=="L. Tansley" {print substr ($1,3, 99) ) ' grade.txt

Tansley

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

$ awk '{print substr($1,3)}' grade.txt

Tansley

Lulu

Bunny

Troll

Tansley

Передача строк из интерпретатора shell утилите awk

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

В первом примере команда echo передает строку "Stand‑by" утилите awk, которая вычисляет ее длину:

$ echo "Stand‑by" | awk '{print length($0)}'

8

Во втором примере утилита awk получает строку с именем файла и возвращает имя файла без расширения:

$ echo "mydoc.txt" | awk '{print substr($STR,1,5)}'

mydoc

Следующая команда возвращает только расширение файла:

$ echo "mydoc.txt" | awk '{print substr($STR,7)}'

txt

 

9.2.11. Escape–последовательности

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

Например, шаблон поиска открывающей фигурной скобки выглядит так:

/\|/

В табл. 9.5 перечислены Escape–последовательности, распознаваемые утилитой awk.

Таблица 9.5. Escape–последовательности утилиты awk

\b Возврат на одну позицию (забой)
\f Прокрутка страницы
\n Новая строка
\r Возврат каретки
\t Горизонтальная табуляция
\ddd Восьмеричный код символа
\c Любой другой специальный символ. Например, запись \\ соответствует символу обратной косой черты

В следующей команде сначала отображается фраза "May Day", в которой слова разделены символом табуляции, а затем выводятся два символа новой строки, вследствие чего образуется пустая строка. Потом отображается слово "May", а за ним -cлово "Day", каждая буква которого представлена ASCII–кодом: 'D' — 104, 'а'- 141,'у'- 171.

$ awk BEGIN {print "May\tDay\n\nMay \104\141\171"}'

May Day

May Day

 

9.2.12. Команда printf

Во всех примерах, с которыми мы ознакомились, данные выводились на экран с помощью команды print без какого‑либо форматирования. В awk имеется намного более мощная команда printf, аналог одноименной функции языка С, позволяющая задавать правила форматирования выходных данных.

Базовый синтаксис команды таков:

printf "строка_формaтирования", аргументы

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

Таблица 9.6. Спецификаторы форматирования

%c Символ ASCI I; если аргумент является строкой, выводится первый символ строки
%d, %i Целое число
%e Число с плавающей точкой в формате [-]d.dddddde[+-]dd
%E Аналогичен спецификатору %e, но знак экспоненты представлен символом 'E', а не 'e'
%f Число с плавающей точкой в формате [~}ddd.dddddd
%G Тип преобразования будет %e или %f в зависимости от того, какой результат короче; вывод незначащих нулей подавляется Аналогичен спецификатору %g, но экспоненциальный формат будет представлен спецификатором %E, а не %e .
%o Восьмеричное число без знака
%s Строка символов
%x Шестнадцатеричное число без знака (используются шестнадцатеричные
цифры а, b, с, d, e, f)
Аналогичен спецификатору %x, но используются шестнадцатеричные
цифры А, В, С, D, E, F
%% Отображается символ '%', интерпретации аргумента не происходит

В состав спецификаторов форматирования могут входить различные модификаторы, определяющие дополнительные особенности форматирования (табл. 9.7). Модификатор помещается между символом '%' и управляющим символом.

Таблица 9.7. Дополнительные модификаторы

- Аргумент выравнивается по левому краю поля вывода (по умолчанию принято правостороннее выравнивание)
пробел Если аргумент является положительным числом, перед ним ставится
пробел, а если отрицательным — знак минус
+ Если аргумент является числом, ему всегда предшествует знак плюс,
даже если это положительное число
# Выбирается альтернативная форма спецификатора:
%о — восьмеричному числу предшествует ведущий ноль;
%x — шестнадцатеричному числу предшествует запись 0x;
%x — шестнадцатеричному числу предшествует запись Оx;
%e, %E, %f — число всегда содержит десятичную точку;
%g, %G — вывод незначащих нулей не подавляется
0 Если длина поля вывода больше, чем число символов в представлении
аргумента, аргумент дополняется нулями, а не пробелами
ширина Ширина поля вывода; если длина поля больше, чем число символов
в представлении аргумента, аргумент по умолчанию дополняется
пробелами
. точность Точность представления аргумента:
%е, %е, %f — число символов после десятичной точки;
%g, %g — максимальное число значащих цифр;
%d, %o, %i, %u, %x, %X — минимальное число выводимых цифр;
% s — максимальное число выводимых символов

Преобразование символов

Чтобы узнать, ASCII–код какого символа равен 65, можно с помощью команды echo направить строку "65" утилите awk и затем передать ее в качестве аргумента команде printf со спецификатором %с. Команда printf выполнит преобразование автоматически:

$ echo "65" | awk '{printf "%c\n", $0}'

A

Как видите, это символ 'A'. Обратите внимание на наличие в команде символа новой строки (\n). Необходимость в нем объясняется тем, что команда printf по умолчанию не записывает этот символ в выходной поток. Если не указать Escape–последовательность \n, сразу после буквы 'А' в той же строке будет отображено приглашение интерпретатора shell, что может смутить пользователя, работающего в данный момент за терминалом.

Конечно, код символа может быть указан непосредственно в команде printf:

$ awk 'BEGIN {printf "%c\n", 65}'

A

Форматированный вывод

Предположим, необходимо отобразить имена учеников и их идентификаторы, причем имена должны быть выровнены по левому краю и размещаться в поле длиной 15 символов. В конце строки форматирования стоит символ новой строки (\n(,

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

$ awk '{printf "%-l5s %d\n", $1, $3}' grade.txt

M. Tansley 48311

J. Lulu 48311

P. Bunny 4 8

J. Troll 4842

L. Tansley 4712

 

9.2.13. Передача переменных утилите awk

Переменные можно создавать не только в сценарии awk, но и непосредственно в командной строке. Формат вызова утилиты awk в этом случае таков:

awk 'сценарий' переменная=значение входной_файл

В следующем примере переменная AGE создается в командной строке и инициализируется значением 10. Показанная команда находит студентов, возраст которых не превышает 10 лет.

$ awk '{if($5 < AGE) print $0}' AGE=10 grade.txt

M. Tansley 05/99 48311 Green 8 40 44

J. Lulu 06/99 48317 green 9 24 26

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

Имя Число блоков . Занято блоков Свободно блоков Процент Точка монтирования
файловой системы занятых
блоков
Столбец 1 2 3 4 5 6

Чтобы узнать, в какой из имеющихся файловых систем объем свободного пространства ниже критической отметки, следует передать выходные данные команды df утилите awk и последовательно сравнить значения в четвертом столбце с пороговым значением. В следующей командной строке пороговое число задано в виде переменной TRIGGER, которая равна 56000:

$ df -k | awk '$4 ~ /^[0-9]/ {if($4 < TRIGGER) print $1"\t"$4)' TR1GGER=56000

/dos 55808 /apps 51022

Опция — к команды df устанавливает размер блока равным 1 Кб (наиболее привычный для пользователя режим), так как в системе может быть задан другой размер блока. Проверка $4 ~ /^[0-9]/ позволяет отсечь заголовок отчета команды df, содержащий в четвертом столбце строку "Available".

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

отфильтровать результаты работы команды who и узнать к какому терминалу подключены: ,

$ who | awk '{if($1==user) print $1 " you are connected to " $2}' user=$LOGNAME

root you are connected to ttyp1

Здесь локальная переменная user инициализируется значением переменой среды $LOGNAME, которая хранит регистрационное имя текущего пользователя.

 

9.2.14. Файлы сценариев

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

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

$ awk 'tot+=$6; END {print "Club student total points; " tot}' grade.txt

Создадим на ее основе файл, который назовем student_tot.awk. Расширение awk является общепринятым соглашением относительно именования файлов сценариев awk. Вот текст этого файла:

#!/bin/awk -f

#Все строки комментариев должны начинаться с символа '#'.

#Имя файла: student_tot.awk

#Командная строка: student_tot.awk grade.txt

#Вычисление суммарного и среднего рейтинга учеников секции.

#Сначала выводим заголовок.

BEGIN {

print "Student Date Member Grade Age Points Max"

print "name joined number gained point available"

print "================================================================="

}

# Суммируем рейтинг учеников.

tot+=$6

# В завершение выводим суммарный и средний рейтинг.

END {

print "Club student total points: " tot print "Average club student points: " tot/NR }

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

Ключевым моментом сценария является первая строка, выглядящая как комментарий:

#!/bin/awk -f

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

#!/путь/программа [командная_строка]

Выражение #! называется "магической" последовательностью. Подразумевается, что, во–neрвых, система, в которой запускается, сценарий, распознает эту последовательность, а, во–вторых, указанная программа воспринимает символ '#' как признак комментария. Это справедливо в отношении всех современных интерпретаторов shell, а также программ perl, awk. и sed, для которых чаще всего создаются автономные сценарии.

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

   1. Первая строка сценария заменяет собой командную строку, из нее удаляется "магическая" последовательность;

   2. Предыдущая командная строка передается новой командной строке в качестве аргумента.

В нашем случае это означает, что при запуске сценария вместо команды

$ student_tot.awk grade.txt

в действительности выполняется такая команда:

$ /bin/awk -f student_tot.awk grade.txt

Опция -f утилиты awk говорит о том, что выполняемые команды находятся в указанном вслед за ней файле.

После создания файл student_tot.awk необходимо сделать исполняемым с помощью команды

$ chmod u+x student_tot.awk

Вот результаты работы сценария:

$ student_tot.awk grade.txt

Student name Date joined Member number Grade Age Points gained Max
point available
M. Tansley 05/99 18311 Green 8 40 44
J. Lulu 06/99 48317 green 9 24 26
P. Bunny 02/99 48 Yellow 12 35 28
J. Troll 07/99 4842 Brown-3 12 26 26
L. Tansley 05/99 4712 Brown~2 12 30 28
Club student total points: 155
Average Club Student points: 31

Использование переменной FS в сценариях awk

Если просматривается файл, в котором разделителем полей является символ, отличный от пробела, например '#' или ':', это легко учесть, указав в командной строке опцию ' -F:'

$ awk -F: '{print $0}' входной_файл

Аналогичную функцию выполняет переменная FS, которую можно установить непосредственно в сценарии. Следующий сценарий обрабатывает файл /etc/passwd, выводя на экран содержимое первого и пятого полей, включающих соответственно имя пользователя и описание роли пользователя в системе. Поля в этом файле разделены символом двоеточия, поэтому в процедурной части шаблона begin устанавливается переменная FS, значение которой заключается в двойные кавычки:

$ cat passwd.awk

#!/bin/awk -f

# Имя файла: student_tot.awk

# Командная строка: passwd.awk /etc/passwd

# Вывод содержимого первого и пятого полей файла паролей.

BEGIN {

FS=":" }

{print $1"\t"$5}

Вот возможные результаты работы этого сценария:

$ passwd.awk /etc/passwd

root Special Admin login

xdm Restart xda Login

sysadm Regular Admin login

daemon Daemon Login for daemons needing permissions

Передача переменных сценариям awk

При передаче переменной сценарию awk формат командной строки таков:

awk файл_сценария переменная=значение входной_файл

Следующий небольшой сценарий сравнивает количество полей в каждой записи входного файла с заданным в командной строке значением. Текущее количество полей хранится в переменной NF. Сравниваемое значение передается сценарию в виде переменной МАХ.

$ cat fieldcheck.awk

#!/bin/awk -f

#Имя файла: fieldcheck.awk

#Командная строка: fieldcheck.awk MAX=n [FS=<разделитель>] имя_файла

#Проверка числа полей в записях файла.

{if(NF != МАХ)

print "line " NR " does not have " MAX " fields"}

При запуске этого сценария вместе с файлом /etc/passwd, содержащим семь полей, необходимо указать следующие параметры:

$ fieldcheck.awk МАХ=7 FS=":" /etc/passwd

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

$ cat age.awk

#!/bin/awk -f

# Имя файла: age.awk

# Командная строка: age.awk AGE=n grade.txt

# Вывод информации об учениках, чей возраст ниже заданного,

{if($5 < AGE) print $0}

$ age.awk AGE=10 grade.txt

M. Tansley 05/99 48311 Green В 4 0 4 4 J. Lulu 06/99 18317 green 9 24 26

 

9.2.15. Массивы

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

$ awk 'BEGIN {print split("123#456#678", myarray, "#")}'

3

Функция split() возвращает число элементов созданного массива myarray. Внутренняя структура массива myarray в этом случае такова:

myarray[1]="123" myаrray[2]="456" myarray[3]="678"

При работе с массивами в awk не требуется заранее их объявлять. Не нужно также указывать количество элементов массива. Для доступа к массиву обычно применяется цикл for, синтаксис которого следующий:

for (элемент in массив) print массив[элемент]

В показанном ниже сценарии функция split() разбивает заданную строку на элементы и записывает их в массив myarray, а в цикле for содержимое массива выводится на экран:

$ cat arraytest.awk

#! /bin/awk -f

#Имя файла: arraytest.awk

#Командная строка: arraytest.awk /dev/null

#Вывод элементов массива.

BEGIN {

record="l23#456#789";

split(record, myarray, "#") } END {

for (i in myarray) print myarray[i]

}

Для запуска сценария в качестве входного файла следует указать устройство /dev/null. Если этого не сделать, утилита awk будет ожидать поступления данных с клавиатуры.

$ arraytest.awk /dev/null

123

789

Статические массивы

В предыдущем примере массив формировался динамически. Следующий пример является более сложным и демонстрирует, как создавать в сценарии статические массивы. Ниже показан входной файл grade_student.txt, включающий информацию об учениках нашей секции каратистов. Записи файла содержат два поля: первое — название пояса, которым владеет ученик, второе — категория ученика (взрослый, юниор). Разделителем полей служит символ '#'.

$ cat grade_student.txt

Vellow#Junior

Orange#Senior

Yellow#Junior

Purple#Junior

Brown-2#Junior

White#Senior

Drange#Senior

Red#Junior

Brown-2#Senior

Yellow#Senior

Red#Junior

Blue#Senior

Green#Senior

Purple#Junior

White#Junior .

Наша задача заключается в том, чтобы прочитать файл и получить следующие сведения:

   1. Сколько учеников имеют желтый, оранжевый и красный пояса?

   2. Сколько взрослых и детей посещают секцию? Рассмотрим такой сценарий:

$ cat belts.awk

#!/bin/awk -f

#Имя файла: belts.awk

#Командная строка: belts.awk grade_student.txt

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

#а также количества взрослых и юных членов секции.

# Задание разделителя и создание массивов.

BEGIN (

FS="#"

# Создание массива, индексами которого являются

#названия поясов. belt["Yellow"] belt["Orange"] belt["Red"]

#Создание массива, индексами которого являются

#названия категорий учеников. student["Junior"]

student["Senior"]

}

#Если значение первого поля совпадает с индексом массива belt,

#содержимое соответствующего элемента массива увеличивается на единицу. {for(colour in belt)

{if ($1==colour)

belt[colour]++ ) }

# Если значение второго поля совпадает с индексом массива student,

# содержимое соответствующего элемента массива увеличивается на единицу, { for (senior_or_junior in student) { if ($2==senior_or_junior)

student [senior_or_junior] ++

) }

# Вывод полученных результатов

END {

for (colour in belt)

print "The club has", belt[colour], colour, "belts" for(senior_or_junior in student)

print "The club has", 'student[senior_or_junior], \ senior_or_junior, "students" }

В процедурной части шаблона begin в переменную FS записывается символ '#" -pазделитель полей во входном файле. Затем создаются два массива, belt и student, индексами которых являются соответственно названия поясов и названия категорий учеников. Оба массива остаются неинициализированными, их тип не определен.

В первом цикле for значение первого поля каждой записи входного файла сравнивается с названием индекса массива belt (индекс равен "Yellow", "Orange" или "Red"). Если обнаруживается совпадение, выполняется приращение элемента, хранящегося в массиве по этому индексу. Поскольку операция ++ (инкремент) определена для целочисленных значений, утилита awk считает, что массив целочисленный, и инициализирует его элементы значением 0.

Во втором цикле for значение второго поля сравнивается с названием индекса массива student (индекс равен "Junior" или "Senior"). Результат операции вычисляется так же, как и в первом цикле.

В процедурной части шаблона end осуществляется вывод итоговых результатов.

$ belts.awk grade_student.txt

The club has 2 Red belts

The club has 2 Orange belts

The club has 3 Yellow belts

The club has 7 Senior students

The club has 8 Junior students

 

9.3. Заключение

Язык программирования awk может показаться сложным для изучения, но если осваивать его на примерах употребления отдельных команд и небольших сценариев, то процесс обучения не будет слишком трудным. В данной главе мы изучили основы awk, не углубляясь в детали, но, тем не менее, получили необходимый минимум сведений, относящихся к этому языку. Утилита awk является важным инструментом shell–программирования, и чтобы применять ее, вам не обязательно быть экспертом по языку awk.

 

ГЛАВА 10

 

Работа с редактором sed

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

В этой главе рассматриваются следующие темы:

   • синтаксис команд sed;

   • поиск строк по номерам и с использованием регулярных выражений;

   • изменение входного текста и добавление текста в выходной поток;

   • примеры команд и сценариев sed.

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

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

 

10.1. Чтение и обработка данных в sed

Общая схема работы редактора sed такова:

   1. Редактор последовательно извлекает строки текста из файла или стандартного входного потока и копирует их в буфер редактирования.

   2. Затем он считывает первую команду из командной строки или сценария, осуществляет поиск строки с указанным номером или строки, соответствующей шаблону, и применяет к ней эту команду.

   3. Второй пункт повторяется до тех пор, пока не будет исчерпан список команд.

 

10.2. Вызов редактора sed

 

Вызвать редактор sed можно тремя способами:

   1. Ввести набор команд sed в командной строке.

   2. Поместить набор команд sed в файл и передать его редактору sed в командной строке.

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

командной строки будет таким:

sed [опции] 'команды' входной_файл

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

Если команды sed помещены в отдельный файл, командная строка примет следующий вид:

sed [опции] — f файл_сценария входной_файл

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

файл_сценария [опции] входной_файл

Когда входной файл не указан, sed будет ожидать поступления данных из стандартного входного потока: с клавиатуры или из канала.

Ниже перечислены основные опции редактора sed и описано их назначение:

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

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

-f Эта опция используется при подключении файла сценария.

 

10.2.1. Сохранение выходных данных

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

$ sed 'команды' входной_файл > выходной_файл

 

10.2.2. Синтаксис команд

Общий синтаксис команд редактора sed таков:

[адрес1 [, адрес2] ] [ ! ] команда [аргументы]

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

Просмотр входного файла по умолчанию начинается с первой строки. Существует два способа адресации строк:

   1. По номерам.

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

В команде может быть указано два адреса, один адрес или ни одного адреса. В следующей таблице описаны все возможные правила отбора строк в зависимости от того, сколько компонентов адреса задано (табл. 10.1).

Таблица 10.1. Правила отбора строк в редакторе sed

Адрес Отбираемые строки
нет адреса Все строки входного файла
X Строка с номером x
х, у Все строки с номерами в диапазоне от x до y
/шаблон/ Все строки, соответствующие шаблону
/шаблон1/, /шаблон 2/ Группа строк, начиная от строки, соответствующей первому шаблону, и заканчивая строкой, которая соответствует второму шаблону; подобных групп во входном файле может быть несколько
/шаблон/,х Группа строк, начиная от строки, соответствующей шаблону, и заканчивая строкой с указанным номером
x,/шаблон/ Группа строк, начиная от строки с указанным номером и заканчивая строкой, соответствующей шаблону
! Все строки, не соответствующие заданному адресу
$ Последняя строка входного файла

Некоторые команды, в частности, a, i, r, q и =, требуют указания только одного адреса.

 

10.2.3. Основные команды редактирования

Ниже представлен список основных команд, имеющихся в редакторе sed (табл. 10.2).

Таблица 10.2. Основные команды sed

p Вывод адресуемых строк
ж Вывод номеров адресуемых строк
а Добавление заданного текста после каждой адресуемой строки
i Вставка заданного текста перед каждой' адресуемой строкой
с Замена адресуемого текстового блока заданным текстом
d Удаление адресуемых строк
s Замена указанного шаблона заданным текстом в каждой адресуемой строке
w Добавление адресуемых строк в указанный файл
r Чтение текста из указанного файла и добавление его после каждой адресуемой строки
q Завершение работы после того, как достигнута адресуемая строка
l Вывод адресуемых строк с отображением непечатаемых символов в виде ASCII-
кодов и переносом длинных строк

С помощью фигурных скобок можно объединить несколько команд в группу. Возможны два синтаксиса группировки:

[адрес1[, адрес2]]{ команда 1

командаN

}

или

[адрес1[,адрес2]] {команда1; …командаN; }

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

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

$ cat quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

 

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

Редактор sed распознает базовые регулярные выражения, которые мы уже рассматривали в главе 7. Дополнительные особенности появляются только в шаблонах поиска и замены в команде s. С помощью операторов \ ( и \) можно сохранить до девяти шаблонов поиска во временном буфере, с тем чтобы в шаблоне замены обратиться к ним с помощью оператора \n, где п — номер сохраненного шаблона. Метасимвол & позволяет в шаблоне замены сослаться на фрагмент строки, соответствующий шаблону поиска.

 

10.4. Вывод строк (команда p)

 

Рассмотрим, как в редакторе sed осуществляется поиск строк и вывод их на экран.

 

10.4.1. Отображение строки по номеру

Команда p (print) имеет такой формат:

[адрес1[,адрес2]]p

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

$ sed '2р' quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

Что было сделано неправильно? Ведь требовалось отобразить только строку номер 2, однако в результате были выведены на экран все строки файла, причем вторая строка — дважды. Причина подобного поведения заключается в том, что по умолчанию редактор sed отображает каждую просматриваемую строку. Чтобы избежать этого, воспользуемся опцией -n:

$ sed -n '2р' quote.txt

It was an evening of splendid music and company.

 

10.4.2. Отображение строк из заданного диапазона

Предположим, требуется вывести строки с номерами от 1 до 3. В этом случае следует указать два адреса, разделенные запятой:

$ sed -n '1,3p' quote.txt

The honeysuckle band played all night long for only §90.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

 

10.4.3. Поиск строк, соответствующих шаблону

В следующем примере показано, как найти строку, содержащую слово "Neave":

$ sed -n '/Neave/p' quote.txt

The local nurse Miss P. Neave was in attendance.

 

10.4.4. Поиск пo шаблону и номеру строки

Если адрес представлен в виде шаблона, редактор sed находит все строки, соответствующие этому шаблону. Как можно уточнить местонахождение строки? Рассмотрим пример. Предположим, требуется найти слово "The" в последней строке файла quote.txt. Если воспользоваться поиском по шаблону, то будет получено две строки:

$ sed -n '/The/p' quote.txt

The honeysuckle band played‑all night long for only $90.

The local nurse Miss P. Neave was in attendance.

Чтобы остановить свой выбор на последней строке, следует указать ее номер перед шаблоном:

$ sed -n '4,/The/p' quote.txt

The local nurse Miss P. Neave was in attendance.

 

10.4.5. Поиск специальных символов

Если требуется найти строку, содержащую символ '$', который в редакторе sed имеет специальное назначение, следует защитить этот символ от интерпретации с помощью обратной косой черты, как показано ниже:

$ sed -n /\$/р' quote.txt

The honeysuckle band played all night long for only $90.

 

10.4.6. Поиск первой строки

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

$ sed -n '1р' quote.txt

The honeysuckle band played all night long for only $90.

 

10.4.7. Поиск последней строки

Чтобы сослаться на последнюю строку входного файла, воспользуйтесь метасимволом '$':

$ sed -n '$p' quote.txt

The local nurse Miss P. Neave was in attendance.

 

10.4.8. Отображение всего файла

Если требуется отобразить весь файл, задайте диапазон строк от первой до последней:

$ sed -n '1,$p' quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

 

10.5. Вывод номеров строк (команда =)

Команда = имеет следующий формат:

[адрес]=

Она предназначена для вывода номера строки, соответствующей заданному адресу. Рассмотрим пример:

$ sed '/music/=' quote.txt

The honeysuckle band played all night long for only $90.

2

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

В данном случае отображается весь файл, причем перед строкой, содержащей слово "music", выводится ее номер. Из этого можно сделать заключение, что команда=выполняется перед тем, как текущая строка будет выведена на экран.

Если же требуется узнать только номер строки, задайте опцию -n:

$ sed -n '/music/=' quote.txt

2

Можно также отобразить и строку, и ее номер. Для этого следует воспользоваться опцией -e, позволяющей указать несколько команд подряд. Первая команда выводит строку, в которой найдено совпадение с шаблоном, а вторая — номер этой строки:

$ sed -n -e '/music/p' -e '/music/=' quote.txt

It was an evening of splendid music and company. 2

 

10.6. Добавление текста (команда а)

Для добавления текста предназначена команда a (append), которая вставляет одну или несколько строк текста после адресуемой строки. Формат команды таков:

[адрес]a\

текст\

текст\

текст

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

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

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

 

10.7. Создание файла сценария

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

Создайте новый файл с именем append.sed и добавьте в него показанные ниже команды:

$ cat append.sed

#!/bin/sed -f

/company/a\

Then suddenly it happened.

Теперь сделайте этот файл исполняемым:

$ chmod u+x append.sed

и запустите его на выполнение:

$ append.sed quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

Then suddenly it happened.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

Если вместо показанного результата будет выдано сообщение об ошибке "command not found" (команда не найдена), значит, переменная среды $PATH, которая содержит список имен каталогов, просматриваемых в поиске исполняемых файлов, не включает имя текущего каталога. В этом случае необходимо явно указать, что исполняемый файл находится в текущем каталоге:

$ ./append.sed quote.txt

Рассмотрим, что делает сценарий append.sed. Первая его строка является системной командой, которая указывает, какая программа выполняет данный сценарий. Формат этой команды мы уже рассматривали при знакомстве с файлами сценариев awk в предыдущей главе. Как и утилита awk, редактор sed, как правило, находится в каталоге /bin.

Далее в сценарии находится команда а, которая ищет во входном файле строку, содержащую слово "company", и вставляет после нее предложение "Then suddenly it happened".

 

10.8. Вставка текста (команда i)

Команда i (insert) аналогична команде а, только вставляет текст не после, а перед адресуемой строкой. Как и при добавлении текста, допускается указание только одного шаблона адреса. Ниже приведен общий формат команды:

[адрес]i\

текст\ текст\

текст

В следующем сценарии предложение "Utter contusion followed" вставляется перед строкой, содержащей слово "attendance":

$ cat insert.sed

#! /bin/sed -f /attendance/i\ "Utter confusion followed.

Результаты работы данного сценария будут такими:

$ insert.sed quote.txt

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

Utter confusion followed.

The local nurse Miss P. Neave was in attendance.

Для указания места вставки текста можно было бы воспользоваться номером строки, в данном случае 4:

#!/bin/sed -f

4i\

Utter confusion followed.

 

10.9. Изменение текста (команда с)

Команда с (change) заменяет новым текстом каждую адресуемую строку. Если выбрана группа строк, вся группа заменяется одной копией текста. Формат команды с таков:

[адрес1[,адрес2]]c\ текст\

текст\

текст

В следующем примере первая строга файла quote.txt заменяется новой строкой:

$ cat change.sed

#! /bin/sed -f

1c\

The Office Dibble band played well.

Прежде чем выполнять этот сценарий, не забудьте сделать его исполняемым:

$ chmod u+x change.sed $ change.sed quote.txt

The Office Dibble band played well. It was an evening of splendid music and company. Too bad the disco floor fell through at 23.10. The local nurse Miss P. Neave was in attendance.

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

$ cat mix.sed

#! /bin/sed -f

# Изменяем строку номер 1

1c\

The Dibble band were grooving.

# Вставляем строку

/evening/i\

They played some great tunes.

# Изменяем последнюю строку

3c\

Nurse Neave was too tipsy to help.

# Добавляем строку после строки номер 3

3a\

Вот что получится в результате выполнения этого сценария:

$ mix.sed quote.txt

The Dibbse band were grooving.

They played some great tunes.

It was an evening of splendid music and company

Too bad the disco floor fell through at 23:10.

Where was the nurse to help?

Nurse Neave was too tipsy to help.

 

10.10. Удаление текста (команда d)

Для удаления текста предназначена команда d (delete), имеющая следующий

формат:

'адрес1[, адрес2]'d

Адрес может быть указан в виде номера строки или регулярного выражения. Рассмотрим примеры. В первом из них будет удалена первая строка входного

файла:

$ sed '1d' quote.txt

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

В следующем примере удаляются строки 1—3:

$ sed '1,3d' quote.txt

The local nurse Miss P. Neave was in attendance.

В этом примере удаляется последняя строка:

$ sed '$d' quote.txt

The honeysuckle band played all night long for only $90

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

Можно также удалить строку, в которой найдено совпадение с регулярным

выражением. В показанном ниже примере удаляется строка, содержащая слово '"Neave":

$ sed '/Neave/d' quote.txt

The honeysuckle band played all night long for only $90

It was an evening of splendid music and company.

Too bad the disco floor fell through at 23:10.

 

10.11. Замена подстроки (команда s)

Команда s (substitute) осуществляет во всех адресуемых строках замену подстроки, соответствующей заданному шаблону, указанной подстрокой. Формат команды

таков:

[адрес1[, адрес2]]s/шаблои_поиска/шаблбон_замены/[флаги]

Ниже перечислены возможные флаги:

g Замена в адресуемой строке каждой подстроки, соответствующей шаблону (по умолчанию заменяется лишь самая первая подстрока каждой адресуемой строки)
n Замена n–й подстроки, соответствующей шаблону (n — любое число в диапазоне от 1 до 512)
p Вывод на экран строки, в которой была произведена замена; если в строке сделано несколько замен, она будет отображена соответствующее число раз
w имя_файла Запись измененной строки в указанный файл

В следующем примере осуществляется замена слова "night" словом "NIGHT":

$ sed -n 's/night/NIGHT/p' quote.txt

The honeysuckle band played all NIGHT long for only $90.

Если требуется удалить из строки символ '$', оставьте шаблон замены пустым (не забывайте, что в редакторе sed знак доллара является метасимволом, поэтому он должен быть защищен обратной косой чертой).

$ sed -n 's/\$//р' quote.txt

The honeysuckle band played all night long for only 90.

Флаг g (global) позволяет выполнить глобальную подстановку шаблона замены на место шаблона поиска в пределах каждой адресуемой строки. Предположим, например, что мы хотим заменить все точки в файле quote.txt восклицательными знаками. Следующая команда выполнит работу не полностью:

$ sed 's/\./!/' quote.txt

The honeysuckle band played all night long for only $90!

It was an evening of splendid music and company!

Too bad the disco floor fell through at 23:10!

The local nurse Miss P.Neave was in attendance.

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

$ sed 's/\./!/g' quote.txt

The honeysuckle band played all night long for only $90!

It was an evening of splendid music and company!

Too bad the disco floor fell through at 23:10!

The local nurse Miss P! Neave was in attendance!

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

"splendid" словом "SPLENDID", а все строки, где была выполнена эта замена, помещаются в файл sed.out.

$ sed -n 's/splendid/SPLENDID/w sed.out' quote.txt

Вот каким будет содержимое этого файла:

$ cat sed.out

It was an evening of SPLENDID music and company.

Ссылка на искомую подстроку с помощью метасимвола &

Метасимвол & позволяет сослаться в шаблоне замены на подстроку, соответствующую шаблону поиска. Например, в следующей команде слово "Miss" либо "miss" заменяется фразой "lovely Miss Joan" или "lovely miss Joan" соответственно:

$ sed -n 's/[Mm]iss/lovely & Joan/p' quote.txt

The local nurse lovely Miss Joan P. Neave was in attendance

Заметьте, что пробелы также являются частью шаблона замены.

 

10.12. Вывод строк в файл (команда w)

Подобно тому как оператор > применяется для перенаправления результатов работы программы в файл, команда w (write) редактора sed позволяет записать в указанный файл строки, отобранные по заданному шаблону адреса. Формат этой команды таков:

[адрес1[, адрес2]]w имя_файла

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

Рассмотрим пример:

$ sed '1,2w sed.out' quote.txt

Здесь содержимое файла quote.txt выводится на экран, а строки с номерами 1 и 2 отправляются в файл с именем sed.out.

$ cat sed.out

The honeysuckle band played all night long for only $90.

It was an evening of splendid music and company,

В следующем примере осуществляется поиск строки, содержащей слово "Neave", и если такая строка найдена, она записывается в файл sed.out.

$ sed -n '/Neave/w sed.out' quote.txt

$ cat sed.out

The local nurse Miss P. Neave was in attendance.

 

10.13. Чтение строк на файла (команда r)

В процессе обработки входного файла редактор sed позволяет читать текст из другого файла и добавлять его к текущему содержимому буфера, размещая после каждой строки, соответствующей шаблону адреса. Формат предназначенной для этого команды r (read) таков:

[адрес]r имя_файла

Давайте создадим небольшой файл с именем sedex.txt.

$ cat sedex.txt

Boom boom went the music.

В следующем примере содержимое этого файла выводится на экран после строки файла quote.txt, содержащей слово "company":

$ sed '/company/r sedex.txt' quote.txt

The honeysuckle band played all night for only $90.

It was an evening of splendid music and company.

Boom boom went the music.

Too bad the disco floor fell through at 23:10.

The local nurse Miss P. Neave was in attendance.

 

10.14. Досрочное завершение работы (команда q)

Иногда требуется завершить работу редактора sed сразу же после нахождения первого совпадения с шаблоном. Эту задачу решает команда q (quit), имеющая следующий формат:

[адрес]q

Обратимся к примеру. Допустим, требуется осуществить поиск строки, содержащей такой шаблон:

/\<.a.\{0,2\}\>/

Этому шаблону соответствует любое слово (выражение \< обозначает начало слова, а выражение \> — его конец(, в котором вторым символом является буква 'a', а за ней идет не более двух символов. В файле quote.txt таких слов четыре:

   • строка 1 — band,

   • строка 2 — was,

   • строка 3 — bad,

   • строка 4 — was.

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

$ sed */\<.a.\{0,2\)\>/q' quote.txt

The honeysuckle band played all night long for only $90.

 

10.15. Отображение управляющих символов (команда l)

Иногда даже в текстовых файлах содержатся различного рода непечатаемые символы. Это может быть следствием неправильного ввода данных в текстовом редакторе или ошибок конвертации при загрузке файлов из других систем. При выводе таких файлов. на экране могут быть получены странные результаты, когда вместо непечатаемого символа отображается один или несколько обычных символов непонятного происхождения. Разобраться в таких ситуациях помогает команда cat -v, которая помечает начало замещающей последовательности символом '^' (знак крышки). Предположим, вы обнаружили незнакомый файл func.txt и хотите узнать его содержимое:

$ cat func.txt

This is the F1 key:P This is the F2 key:Q

Символы 'P' и 'Q' на концах строк кажутся подозрительными. Попробуем применить команду cat -v:

$ cat -v func.txt

This is the Fl key:^[OP This is the F2 key:^[OQ

Так и есть! Это не буквы 'Р и 'Q', а управляющие символы, хотя и непонятно, с помощью каких клавиш они были сгенерированы.

Аналогичным образом будет вести себя и редактор sed при работе с данным; файлом. Если вы попытаетесь просмотреть его содержимое, будет выдано следующее:

$ sed -n '1,$p' func.txt

This is the F1 key:P This is the F2 key:Q

В редакторе существует команда l (list}, аналог рассмотренной выше команды cat -v. Формат команды l таков:

[адрес1[,адрес2]]l

Ее действие равносильно применению команды p, но при этом все непечатаемые символы заменяются восьмеричными ASCII–кодами (кроме того, длинные строки, выходящие за пределы экрана, разбиваются на части, а конец каждой строки помечается символом '$'). Вот что получится, если применить эту команду к файлу func.txt.

$ sed -n '1,$1' func.txt

This is the Fl key:\033OP$ This is the F2 key:\033OQ$

Теперь ситуация немного проясняется. По таблице ASCII–кодов можно узнать, что восьмеричный код 033 соответствует непечатаемому символу esc. Именно он в выводе команды cat -v обозначается как ^ [. За ним идут два обычных символа: сначала 'О', затем 'Р' либо 'Q'. Таким образом, мы имеем дело с двумя клавишами, каждая из которых сгенерировала трехсимвольную последовательность: одна — esc‑O–р, а вторая — esc‑o–q. Когда файл отображается в обычном режиме, каждому символу соответствует одно знакоместо, поэтому первые два символа последовательности отбрасываются и остается последний: 'P' и 'Q' соответственно.

Нет системной команды, которая позволяла бы узнать, какие клавиши генерируют эти коды. Подобные сведения находятся в базах данных termcap и terminfo, хранящих установки терминала, но знакомство с этими базами данных выходит за рамки нашей книги. Проще всего пойти экспериментальным путем: ввести команду cat или cat -v без указания входного файла, с тем чтобы попробовать самостоятельно определить, нажатия каких клавиш приводят к отображению на экране нужных последовательностей символов. В нашем случае последовательность ESC‑O–P генерируется клавишей [F1], а последовательность ESC‑O–Q — клавишей [F2], хотя в общем это зависит от установок терминала.

Если вам интересно, как можно создать файл func.txt, ниже описана возможная процедура:

   1. Загрузите редактор vi.

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

   3. Нажмите [Ctrl+V], при этом появится символ '^'

   4. Нажмите клавишу [F1], при этом отобразится последовательность [ор.

   5. Нажмите клавишу [Enter] и повторите пункты 2—4 для второй строки файла (в конце нажимается клавиша [F2]).

   6. Нажмите клавишу [Esc], чтобы выйти из режима вставки.

   7. Введите команду w func.txt, чтобы сохранить файл.

   8. Введите команду q, чтобы выйти из редактора vi.

 

10.16. Дополнительные примеры использования редактора sed

 

Выше были описаны основные команды sed. Далее мы рассмотрим ряд практических примеров применения редактора sed.

 

10.16.1. Обработка управляющих символов

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

$ cat -v dos.txt

12332##DISO##45.12^M C0332##LPSO##23.11^M

01299##USPD##34.46^M

Вот что необходимо сделать:

   1. Заменить все знаки решетки ('#'( пробелом.

   2. Удалить из первого поля каждой строки все ведущие нули.

   3. Удалить в конце каждой строки последовательность ^M, генерируемую непечатаемым символом CR — возврат каретки (ASCII–код 13).

Примечание:

В ряде систем, в которых мне приходилось выполнять подобные преобразования, в конце строки стоит символ перевода строки LF (ASCII–код 10, отображается как ^@). Подобная проблема нередко возникает при передаче файлов между двумя системами, в одной из которых символ новой строки (\n) заменяется последовательностью CR/LF, a в другой — только одним из этих двух управляющих символов.

Задача 1. Удаление всех символов решетки реализуется без особого труда. Для этого достаточно выполнить команду замены s с флагом глобальной подстановки д, указав при этом, что один или более символов '#', идущих подряд, должны быть заменены пробелом:

$ sed 's/##*/ /g' dos.txt | cat -v

12332 DISO 45.12^М

00332 LPSO 23.11^М

01299 USPD 34.4б^M

Задача 2. Для удаления всех ведущих нулей следует в команде s оставить шаблону замены пустым, а шаблон поиска задать таким: /^0*/. Он означает, что требуется найти любое количество нулей, стоящих в начале строки.

$ sed 's/^0*//' dos.txt | cat -v

12332##DISO##45.12^M

332##LPSO##23.11^M

1299##USPD ##34.46^M

Задана 3. Чтобы избавиться от управляющих символов ^M в конце строк, необходимо. также применить команду s, оставив шаблон замены пустым. А вот при формировании шаблона поиска следует учесть, что мы имеем дело с непечатаемым символом. Нельзя просто ввести символы '^' и 'M', так как полученный шаблон будет означать, что мы ищем букву 'М', стоящую в начале строки. Чтобы действительно создать нужный нам непечатаемый символ, необходимо нажать [Ctrl+V], а затем — клавишу [Enter]. Результат будет выглядеть как регулярное выражение ^M, но на самом деле это экранное представление символа CR.

$ sed 's/^M//' dos.txt | cat -v

12332##DIS0##45.12 0D332##LPSO##23.11 01299##U5PD#t34.46

Теперь можно попробовать объединить три команды в одну с помощью опции -e;

$ sed -e 's/^0*//' -e 's/^M//' -e 's/##*/ /g' dos.txt | cat -v

12332 DISO 45.12

332 LPSO 23.11

1299 USPD 34.46

Выходные данные редактора sed передаются по каналу команде cat -v, которая позволяет убедиться, что вся работа, включая удаление непечатаемых символов, выполнена правильно.

Приведенные выше команды удобнее поместить в файл сценария. Назовем его dos.sed. Вот его текст:

$ cat dos.sed

#! /bin/sed -f

#Имя: dos.sed

#Командная строка: dos.sed dos.txt

#Избавляемся от символа решетки

s/##*/ /g

#Удаляем ведущие нули

s/^0*//

#Удаляем символы возврата каретки

s/^M//

На входе данного сценария следует указать файл dos.txt. Сценарий "исправит" этот файл и выведет результат на экран. Если же ввести следующую командную строку:

$ dos.sed dos.txt | tee dos.txt

то результат будет записан обратно в файл dos.txt.

 

10.16.2. Обработка отчетов

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

Database Size (MB) Date created
GOSOUTH 2244 5632 12/11/9? 8/9/99
TRISUD

(2 rows affected) Из этой информации нас, предположим, интересуют только имена баз данных, находящиеся в первой колонке. Чтобы их извлечь, необходимо выполнить следующую последовательность действий:

   1. Удалить пунктирную линию с помощью команды s/--*//g.

   2. Удалить все пустые строки с помощью команды /^$/d.

   3. Удалить последнюю строку с помощью команды $d.

   4. Удалить первую строку с помощью команды 1d.

   5. Отобразить первый столбец с помощью команды awk '{print $1}'. Ниже приведена соответствующая цепочка команд:

$ sed 's/--*//g' -e '/^$/d' -e '$d' -e '1d' sql.txt | awk '{print $1}'

GOSOUTH TRISUD

 

10.16.3. Добавление текста

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

$ cat ok.txt

АС456

АС492169

АС9967

АС88345

Наша задача состоит в добавлении слова "Passed" (обработано) в конец каждой строки. Решить ее несложно. Достаточно в шаблоне поиска указать метасимвол '$', означающий конец строки, а в шаблоне замены — пробел и искомое слово:

$ sed 's/$/ Passed/' ok.txt

АС456 Passed

АС492169 Passed

AC9967 Passed

AC8B345 Passed

 

10.16.4. Удаление начальной косой черты в путевом имени

Ниже показано, как с помощью редактора sed можно быстро удалить начальную косую черту из имени текущего каталога:

$ cd /usr/local

$ echo $PWD | sed 's/^\///g'

usr/local

Имя текущего каталога хранится в переменной среды Spwd. Эта переменная обновляется всякий раз, когда выполняется команда cd. Команда echo передает значение переменной по каналу редактору sed, который выполняет несложную обработку: находит в начале строки (метасимвол '^'( символ косой черты (защищен от интерпретации обратной косой чертой) и заменяет его пустой подстрокой.

 

10.17. Заключение

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

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

 

ГЛАВА 11

 

Дополнительные утилиты работы с текстом

Существует большое количество утилит, предназначенных для сортировки, объединения, разбиения и прочей обработки текстовых файлов. В этой главе мы познакомимся с такими командами:

   • sort;

   • uniq;

   • join;

   • cut;

   • paste;

   • split,

 

11.1. Сортировка файлов с помощью команды sort

 

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

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

 

11.1.1. Опции команды sort

Команда sort имеет следующий формат:

sort [опции] [входные_файлы]

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

Таблица 11.1. Основные опции команды sort

Проверка того, отсортирован ли файл; сортировка не производится
-m Объединение отсортированных файлов; сортировка не производится
-u Удаление повторяющихся строк
Вывод результата не на экран, а в указанный файл
-b Игнорирование начальных пробелов в полях сортировки
-n Включение режима числовой сортировки
-t Задание разделителя полей
-r Сортировка в обратном порядке
+поз1[-поз2] Ключом сортировки становится строка, начинающаяся в позиции
поз1 и заканчивающаяся перед позицией поз2 (или в конце текущей строки, если второй параметр не указан)*; номера полей и позиции начальных символов отсчитываются от нуля
-k поз1[, поз2] Ключом сортировки становится строка, начинающаяся в позиции
поз1 и заканчивающаяся в позиции поз2 (или в конце текущей строки, если второй параметр не указан) [2] ; номера полей и позиции начальных символов отсчитываются от единицы
-n Поле с номером я не должно сортироваться; значение n отсчитывается от нуля

 

11.1.2. Сохранение результатов сортировки

Чтобы сохранить результаты сортировки, укажите опцию -o и выходной файл. Можно также воспользоваться традиционным методом переадресации с помощью оператора >. В следующем примере результаты сортировки сохраняются в файле results.out

$ sort video.txt > results.out

 

11.1.3. Тестовый файл

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

   1. Название фильма.

   2. Код фирмы–дистрибьютора.

   3. Количество заказов за последний квартал.

   4. Количество заказов за последний год.

$ cat video.txt

Boys in Company C:HK:192:2192 Alies:HK:119:1982

The Hill:KL:63Yf972

Aliens:НК:532:4892

Star Wars:HK:301:4102

A Few Good Men:KL:445:5851

Toy Story:HK:239:3972

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

 

11.1.4. Индексация полей

При работе с командой sort не следует забывать, что команда обращается к первому полю как к полю 0, следующее поле имеет номер 1 и т. д. Если номера полей не указаны, вся строка считается единым полем. Обратимся к тестовому файлу и уточним, каким образом команда sort разбивает файл на поля:

Поле 0 Поле 1 Поле 2 Поле 3
Star Wars HK 301 4102
A Few Good Men KL 445 5851

 

11.1.5. Проверка факта сортировки файла

Каким образом можно узнать, отсортирован ли данный файл? Если он содержит, например, около 30 строк, то достаточно его просмотреть. А если в нем 400 строк? Примените команду sort -c, которая сама определит, отсортирован ли файл:

$ sort -с video.txt

sort: disorder on video.txt

Команда sort считает, что файл не отсортирован. Давайте отсортируем его и посмотрим, что будет:

$ sort video.txt | sort -c $

Сообщение не появилось, таким образом, файл является отсортированным.

 

11.1.6. Простейшая сортировка

В простейшем случае, чтобы отсортировать файл, достаточно передать его имя команде sort. Сортировка будет выполнена по строкам:

$ sort video.txt

A Few Good Men:KL:445:5851

A. Iien:HK:119:1982

Aliens:HK:532:4892

Boys in Company C:HK:192:2]92

Star Wars:HK:301:4102

The tfili:KL:63:2972

Toy Story. HK:239:3972

 

11.1.7. Сортировка а обратном порядке

Если необходимо отсортировать строки не по возрастанию, а по убыванию, задайте опцию -r:

$ sort -r video.txt

Toy Story:HK:239:3972

The H111:KL:63:2972

Star Wars:HK:301:4102

Boys in Company С:HK:192:2192

Aliens:HK:532:4892

Alien:HK:119:1982

A Few Good Men:KL:445:5851

 

11.1.8. Сортировка по заданному полю

В следующем примере файл сортируется по кодам фирм–дистрибьюторов. Поскольку требуемая информация находится во втором поле (ключ сортировки 1(, следует указать опцию +1. Кроме того, необходимо задать разделитель полей с помощью опции -t:, чтобы команда sort знала, как найти второе поле.

$ sort -t: +1 video.txt

Alien:HK:119:1982

Boys in Company С:HK:192:2192

Toy Story:HK:239:3972

Star Wars:HK:301;4102

Aliens:HK:532:4892

A Few Good Men:KL:445:5851

The Hill; KL:63:2972

Обратите внимание на то, что третье и четвертое поля также были отсортированы. Такова стандартная процедура: все последующие поля по умолчанию считаются ключами сортировки, расположенными в порядке убывания приоритета. Причем если вы посмотрите на конечные две строки, то заметите, что к этим полям применялась не числовая, а текстовая сортировка, учитывающая расположение символов в таблице ASCII–кодов. Поэтому поле со значением 445 оказалось расположенным раньше поля со значением 63.

 

11.1.9. Сортировка по числовому полю

Чтобы корректно отсортировать файл по четвертому, числовому, полю, укажите не только ключ сортировки (+3), но и опцию -n, включающую режим числовой сортировки. Следующая команда сортирует список фильмов по объемам проката видеокассет за год:

$ sort -t: +3n video.txt

Alien:HK:119:1982

Boys in Company C:HK:192:2192

The Hill:KL:63:2972

Toy Story:HK:239:3972

Star Wars:HK:301:4102

Aliens:HK:532:4B92

A Few Good Men:KL:445:5851

Таким образом, можно заключить, что фильм "A Few Good Men" ("Несколько хороших парней", 1992 г.) является лидером видеопроката в текущем году.

Примечание:

Несмотря на наличие опции -n, данный пример работает правильно только потому, что четвертое поле является последним в строке. Причина этого объясняется ниже.

 

11.1.10. Сортировка с отбрасыванием повторяющихся строк

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

$ cat video.txt

Boys in Company С:HK:192:2192

Alien:HK:119:1982

The Hill:KL:63:2972

Aliens:HK:532:4892

Star Wars:HK:301:4102

A Few Good Men:KL.445:5851

Toy Story:HK:239:3972

Alien:HK:119:1982

Вот что получится в результате применения команды sort -u:

$ sort -и video.txt

A Few Good Men:KL:445:5851

Alien:HK:119:1982

Aliens:HK:532;4892

Boys in Company С: НК:192:2192

Star Wars:HK:301:4102

The Hill:KL:63:2972

Toy Story:HK:239:3972

 

11.1.11. Задание ключа сортировки с помощью опции -k

Команда sort позволяет задать ключ сортировки немного по–другому. Если воспользоваться опцией — к, то поля (ключи сортировки) можно будет нумеровать, начиная с единицы, а не с нуля, что, в принципе, удобнее. Таким образом, чтобы выполнить сортировку по полю 4, достаточно задать опцию -k4n. Это позволит упорядочить список фильмов по объемам видеопроката за год.

$ sort -t: — k4n video.txt

Alien:HK:119:l982

Boys in Company C:HK:192:2192

The Hill:KL:63:2972

Toy Story:HK:239:3972

Star Wars:HK:30l:4102

Aliens:HK:532:4892

A Few Good Men:KL:445:5851

 

11.1.12. Несколько ключей сортировки

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

Схожая проблема возникает при работе с несколькими ключами сортировки. Рассмотрим такой пример. Предположим, требуется отсортировать список фильмов по кодам дистрибьюторов (второе поле), а затем по названиям фильмов (первое поле). Если сослаться на поля по номерам, получим следующее:

$ sort -t: — k2 -kl video.txt

Alien:HK.119:1982

Boys in Company C:HK:192:2192

Toy Story:HK:239:3972

Star Wars:HK:301:4102

Aliens:HK:532:4892

A Few Good Men:KL:445:5851

The Hill:KL:63:2972

Здесь ссылка на первое поле в действительности означает ссылку на всю строку, т. е. ключ с меньшим приоритетом включает в себя ключ с большим приоритетом, поэтому команда sort ведет себя не так, как можно было бы предположить на первый взгляд. Чтобы исправить ситуацию, необходимо четко указать длину каждого из ключей:

$ sort -t: — k2,2 -k1,1 video.txt

Alien:HK:119:1982

Aliens:HK:532:4892

Boys in Company C:HK:132:2192

Star Wars:HK:301;4102

Toy Story:HK:239:3972

A Few Good Men:KL:445:5851

The Hill:KL:63:2972

Опция -k2,2 ограничивает ключ сортировки вторым полем, а опция -kl,1 — первым.

 

11.1.13. Указание позиции, с которой начинается сортировка

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

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

$ cat video.txt

Boys in Company C:HK48:192:2192 Alien:HK57:H9:1982

The Hill:KL23:63:2972

Aliens:НК11: — 5Э2г4892

Star Wars:HK38:301:4102

A Few Good Men:KL87:445:5851

Toy Story:HK65:239:3972

Теперь мы хотим отсортировать файл по кодам регионов. Вот как можно это сделать:

$ sort -t: — k2.3,2,4n video.txt

Aliens:HK13:532:4892

The Hill:KL23:63:2972

Star Wars:HK38:301:4102

Boys in Company C:HK48:192:2192

Alien:HK57:119:1982

Toy Story:HK65:239:3972

A Few Good Men:KL87:445:5851

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

 

11.1.14. Обработка результатов сортировки с помощью команд head и tail

При работе с большими файлами не обязательно выводить на экран весь файл, если требуется просмотреть только его начало и конец. Существуют удобные команды head и tail, упрощающие подобную задачу. Команда head отбирает первые п строк файла (по умолчанию 10), а команда tail — последние я строк (по умолчанию тоже 10).

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

$ sort -t: — k4 video.txt | head -1

Alien:HK:119:1982

Аналогичным образом можно выяснить, какой фильм чаще всего заказывали в этом году. Формат команды sort останется таким же, но результат будет передан команде tail:

$ sort -t: — k4 video.txt | tail -1

A few Good Men:KL:445:5851

 

11.1.15. Передача результатов сортировки утилите awk

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

$ sort -t: — k4 video.txt | head -1 | \

awk -F: '{print "Worst rental", $1, "has been rented", $3, "times"}'

Worst rental Alien has been rented 119 times

 

11.1.16. Объединение двух отсортированных файлов

Прежде чем объединять два файла, их необходимо отсортировать, иначе результат будет неотсортированным. Предположим, нам прислали файл video2.txt, содержащий дополнения к уже имеющемуся перечню фильмов, причем этот файл отсортирован:

$ cat video2.txt

Crimson Tide:134:2031 Die Hard:152:2981

Необходимо объединить его с файлом video.txt. Для этого нужно предварительно создать отсортированную версию файла video.txt, которую назовем video.sort, а затем применить команду sort с опцией -m:

$ sort -t: — m -k1 video2.txt video.sort

A Few Good Men:KL:445:5851

Alien:HK:119:1982

Aliens:HK:532:4892

Boys in Company C:HK:192:2192

Crimson Tide:134:2031

Die Hard:152:2981

Star Wars:HK:301:4102

The Hill:KL:63:2972

Toy Story:HK:239:3972

 

11.1.17. Дополнительные примеры команды sort

Команда sort может применяться для сортировки имен пользователей в файле /etc/passwd. Достаточно выполнить сортировку содержимого этого файла по первому полю, которое включает регистрационные имена, а затем по каналу передать полученный результат утилите awk. Последняя отобразит содержимое только первого поля данного файла:

$ cat /etc/pasawd | sort -t: — k1 | awk -F: '{print $1}'

adm bin daemon

Команда sort может работать совместно с командой df, выводя на экран информацию об имеющихся файловых системах в порядке убывания процента используемого ими дискового пространства. Ниже приводится образец работы команды df:

$ df

Filesystem Ik‑blocks Used Available Use% Mounted on

/dev/hda5 495714 291027 179086 62% /

/dev/hda1 614672 558896 55776 91% /dos

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

$ df | sed '1d' | sort -b -r -k5

/dev/hda1 614672 558896 55776 91% /dos

/dev/hda5 495714 291027 179086 62% /

 

11.2. Удаление повторяющихся строк с помощью команды uniq

 

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

Эту команду можно рассматривать как вариант опции — и команды sort. Следует, однако, учитывать весьма важное отличие. Опция -u позволяет избавиться от всех одинаковых строк в файле, тогда как команда uniq обнаруживает повторяющиеся строки только в том случае, когда они следуют одна за другой. Если же на вход команды uniq подать отсортированный файл, то действие команд sort -u и uniq будет одинаковым.

Рассмотрим пример. Имеется следующий файл:

$ cat myfile.txt

May Day

May Day

May Day

Going Down

May Day

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

 

11.2.1. Синтаксис

Общий формат команды uniq таков:

uniq опции входной_файл выходной_файл

Ниже перечислены некоторые из ее опций:

-u Отображение только не повторяющихся строк

-d Отображение одной копии каждой повторяющейся строки

-c Удаление повторяющихся строк с выводом перед каждой из оставшихся строк

числа повторений -f n Игнорирование первых п полей; полем считается последовательность непробельных символов, завершающаяся пробелом или табуляцией

В некоторых системах опция -f не распознается, в этом случае вместо нее следует использовать опцию -n, где п — номер поля.

Давайте применим команду uniq к показанному выше файлу myfile.txt.

$ uniq myfile.txt

May Day

Going Down'

May Day

Как уже говорилось, последняя строка не считается повторяющейся. Если же выполнить над файлом команду sort -u, будут получены только две строки:

$ sort -u myfile.txt

Going Down

May Day

 

11.2.2. Определение количества повторений

Указав в команде uniq опцию -c, можно не только отбросить повторяющиеся строки, но и узнать, сколько раз повторяется каждая строка. В следующем примере команда uniq сообщает о том, что первая строка "May Day" встречается три раза подряд:

$ uniq -с myfile.txt

3 May Day

1 Going Down

1 May Day

 

11.2.3. Отображение только повторяющихся строк

Опция -d позволяет отобразить только те строки, которые встречаются несколько раз подряд:

$ uniq -d myfile.txt

Mау Day

 

11.2.4. Проверка уникальности отдельных полей

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

$ cat parts.txt

AK123 OP

OК122 OP

OК999 OP

Если к этому файлу применить команду uniq, будут отображены все строки, поскольку все они разные.

$ uniq parts.txt

AК123 OP

OK122 OP

OK999 OP

Если же выполнить проверку только по второму полю, получим иной результат. Команда uniq сравнит повторяющиеся поля "ОР" и отобразит только одну строку:

$ uniq -f1 parts.txt

OK123 OP

 

11.3. Объединение файлов с помощью команды join

 

Команда join выполняет соединение строк из двух текстовых файлов на основании совпадения указанных полей. Ее действие напоминает операцию join языка SQL. Механизм работы команды таков:

   1. Каждый из двух входных файлов разбивается на поля (по умолчанию разделителем полей является пробел).

   2. Из первого файла извлекается первая строка, а из нее — первое поле (можно указать другое поле).

   3. Во втором файле ищется строка, имеющая такое же первое поле.

   4. Найденная строка, из которой удаляется первое поле, присоединяется к исходной строке, и результат записывается в выходной поток.

   5. Пункты 3 и 4 повторяются до тех пор, пока во втором файле есть строки с совпадающим первым полем.

   6. Пункты 2—6 повторяются для каждой строки первого файла.

Таким образом, в выходной поток попадают только строки, имеющие общие компоненты.

Общий формат команды join таков:

join [опции] входной_файл1 входной_файл2

Рассмотрим некоторые наиболее важные опции этой команды:

-a n Задает включение в выходной поток строк из файла n (n -l или 2), для которых не было найдено ни одного совпадения по указанному полю.
-o формат Задает формат выводимой строки. Параметр формат представляет собой разделенный запятыми или пробелами список спецификаций, каждая из которых, в свою очередь, имеет формат номер_файла. поле. По умолчанию формат выводимой строки таков:
1 — поле, по которому производится объединение;
2 -oставшаяся часть первой строки;
3 -oставшаяся часть второй строки.
-1 поле Объединять строки по указанному полю первого файла (по умолчанию таковым является первое поле)
-2 поле Объединять строки по указанному полю второго файла (по умолчанию таковым является первое поле)
-t символ Задает разделитель полей во входном и выходном потоках

 

11.3.1. Объединение двух файлов

Предположим, имеется два текстовых файла: один называется names.txt и содержит имена пользователей с указанием улиц, на которых они проживают, а другой называется town.txt и содержит имена пользователей с указанием городов, в которых они живут.

$ cat names.txt

M. Golls 12 Hidd Rd

P. Heller The Acre

P. Willey 132 The Grove

T. Norms 84 Connaught Rd

K. Fletch 12 Woodlea

$ cat town.txt

M. Golls Norwich NRD

P. Willey Galashiels GDD

T. Norms Brandon BSL

K. Fletch Mildenhall MAF

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

$ join names.txt town.txt

M. Golls 12 Hidd Rd Norwich NRD

P. Willey 132 The Grove Galashiels GDD

T. Norms 6-3 Connaught Rd Brandon BSL

K. Fletch 12 Woodlea Mildenhall MAF

Как видите, пользователь P. Heller, для которого нет строки во втором файле, в результаты работы команды join не попал.

 

11.3.2. Включение несовпадающих строк

Если требуется все‑таки включить информацию о пользователе P. Heller в выходные данные, воспользуйтесь опцией -a. Поскольку исходная строка находится в первом файле, параметром данной опции будет цифра 1:

$ join -a1 names.txt town.txt

M. Golls 12 Hidd Rd Norwich NRD

P. Heller The Acre

P. Willey 132 The Grove Galashiels GDD

T. Norms 84 Connaught Rd Brandon BSL

K. Fletch 12 Woodlea Mildenhall MAF

В общем случае, чтобы включать в результаты несовпадающие строки из обоих файлов, применяйте команду join -a1 -a2.

 

11.3.3. Задание формата вывода

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

$ join -о 1.1,2.2 names.txt town.txt

M. Golls Norwich

P. Willey Galashiels

T. Norms Brandon

K. Fletch Mildenhall

 

11.3.4. Выбор ключевого поля

He всегда первое поле является общим для обоих файлов. Рассмотрим пример. Имеются два файла:

$ cat pers

P. Jones Office Runner ID897

S. Round UNIX admin ID667

L. Clip Personl Chief ID982

$ cat pers2

Dept2C ID897 6 years

Dept3S ID667 2 years

Dept5Z ID982 1 year

Файл pers содержит имена, названия должностей и личные идентификационные номера служащих фирмы. Файл pers2 содержит для каждого из служащих код отдела, в котором он работает, идентификационный номер и стаж работы на фирме. В данном случае требуется выполнить соединение строк по номеру служащего. Он хранится в четвертом поле первого файла и во втором поле второго файла. Задать их в команде join можно с помощью опции -n т, где п — номер файла, a m — номер поля.

Ниже приведена соответствующая команда и результат ее выполнения:

$ join -1 4 -2 2 pers pers2

ID897 P. Jones Office Runner Dept2C 6 years

ID667 S. Round UNIX admin Dept3S 2 years

ID982 L. Clip Personl Chief Dept5Z 1 year

При работе с командой join следует быть внимательным, вычисляя номер нужного поля. Можно посчитать, что доступ реализуется к полю 4, а с точки зрения команды join это поле 5. В итоге будут получены неправильные результаты. Чтобы проверить, содержит ли поле с указанным номером предполагаемые данные, воспользуйтесь утилитой awk:

$ awk '{print $4}' имя_файла

 

11.4. Вырезание текста с помощью команды cut

 

Команда cut позволяет вырезать фрагменты строк из текстовых файлов или из стандартного входного потока. Извлеченный подобным образом текст направляется в стандартный выходной поток. Общий формат команды cut таков:

cut [опции] файлы…

Рассмотрим основные опции этой команды:

-c список Определяет, какие символы извлекаются из каждого входного файла

-f список Определяет, какие поля извлекаются из каждого входного файла

-d Задает разделитель полей

Параметр список в опциях -c и -f представляет собой разделенный запятыми список диапазонов символов или полей соответственно. Диапазон может быть задан в одной из четырех форм:

-n В выходной поток включается каждый n–й символ (поле) каждой строки

каждого входного файла

n- Диапазон формируется от n–го символа (поля) до конца строки

n–m Диапазон формируется от n–го символа (поля) до m–го символа (поля) включительно

-m Диапазон формируется от начала строки до m–го символа (поля)

 

11.4.1. Задание разделителя полей

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

$ cat pers

P. Jones:Office Runner:ID897

S. Round:UNIX admin:ID667

L. Clip:Personl Chief:ID982

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

$ cut -d: — f3 pers

ID897

ID667

ID982

Опция -d: говорит о том, что поля в файле разделяются двоеточием. Опция -f 3 задает выборку третьего поля.

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

$ cut -d: — f1,3 pers

P. Jones:ID897

S. Round:ID667

L. Clip:ID982

А вот как можно извлечь из каталога /etc/passwd регистрационные имена пользователей и имена их начальных каталогов, хранящиеся в полях 1 и 6 соответственно:

$ cut -d: — f1,6 /etc/passwd

gopher:/usr/lib/gopher‑data

ftp:/home/ftp

peter:/home/apps/peter

dave:/home/apps/dave

 

11.4.2. Вырезание отдельных символов

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

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

Идентификатор источника содержится в последних трех символах имени файла. Вот примерный список имен файлов:

2231DG

2232DP

2236DK

Извлечение идентификаторов осуществляется с помощью такой команды:

$ 1s 223* | cut -с4–6

1DG

2DP

6DK

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

$ who! cut -c1-8

root

dave

peter

 

11.5. Вставка текста с помощью команды paste

 

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

Команда paste объединяет строки с одинаковыми номерами: сначала берутся первые строки из каждого файла и объединенная строка записывается в выходной поток, затем берутся вторые строки, третьи и т. д. По умолчанию разделителем полей является символ табуляции, если только не указана опция -d, которая позволяют задать иной разделитель.

Формат команды paste таков:

paste [опции] файл…

Рассмотрим опции команды paste:

-d список Сообщает команде paste о необходимости применять вместо табуляции другой разделитель полей. Допускается указывать список разделителей, В этом случае разделители используются циклически: между первыми двумя строками вставляется первый разделитель из списка, между следующими двумя — второй и т. д. Когда список заканчивается, осуществляется возврат к началу списка и процедура повторяется.
-s Задает режим последовательного слияния строк каждого входного файла по отдельности
- Означает выборку строки из стандартного входного потока

 

11.5.1. Определение порядка вставки столбцов

Для иллюстрации процедуры вставки обратимся к следующим двум файлам, полученным путем применения команды cut к рассмотренному выше файлу pers.

$ cat pas1

ID897

ID667

ID9B2

$ cat раs2

P. Jones

S. Round

L. Clip

По умолчанию команда paste вставляет столбцы один за другим:

$ paste pas1 pas2

ID897 P. Jones

ID667 S. Round

ID982 L. Clip

Порядок задания файлов в командной строке играет роль:

$ paste pas2 pas1

P. Jones ID897

S. Round ID667

L. Clip ID982

 

11.5.2. Выбор разделителя полей

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

$ paste -d: pas2 pas1

P. Jones:ID897

S. Round:ID667

L. Clip:ID982

 

11.5.3. Слияние строк

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

$ paste -a pas2 pas1

P. Jones S. Round L. Clip

ID897 ID667 ID982

 

11.5.4. Чтение данных и» стандартного входного потока

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

$ cd /etc

$ ls | paste -d" " - - - -

init.d rc rc.local rc.sysinit rc0.d rc1.d rc2.d rc3.d rc4.d rc5–d rc6.d

Если же нужно отобразить список в одну колонку, воспользуйтесь такой командой:

$ ls | paste -

init. d

rc

rc.local

rc.sysinit

rc0.d

rc1.d

 

11.6. Разделение файла на части с помощью команды split

Команда split позволяет разделять крупные текстовые файлы на более мелкие, Это может оказаться удобным, например, при передаче файлов по сети. Общий формат команды split таков:

split [-размер_выходного_файла] входной_файл [префикс]

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

Имя каждого созданного файла представляется в формате от префикс[аа] до префикс[zz]. По умолчанию префиксом является буква 'x'. Таким образом, команда split создает такую последовательность файлов:

хаа, xab, … xzy, xzz

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

Следующий пример поможет разъяснить сказанное. Допустим, имеется файл bigone.txt, содержащий 2800 строк. В результате выполнения команды split будут сформированы три выходных файла:

Имя файла Размер

xaa 1000

xab 1000

xac 800

Теперь рассмотрим, как изменить размер создаваемых файлов. Ниже показан файл split1, содержащий шесть строк:

$ cat split1

this ls line1

this ls line2

this ls line3

this ls line4

this ls line5

this ls line6

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

$ split -2 split1

Давайте проверим, что было создано (команда ls -lt сортирует список файлов по дате создания, а команда head отбирает из этого списка первые десять элементов):

$ ls -lt | head

total 205

-rw‑r--r-- 1 dave admin 28 Apr 30 13:12 xaa

-rw‑r--r-- 1 dave admin 28 Apr 30 13:12 xab

-rw‑r--r-- 1 dave admin 28 Apr 30 13:12 xac

Исходный файл состоит из шести строк. В результате применения к нему команды split были сформированы три файла, содержащих по две строки каждый. Чтобы убедиться в правильности работы команды, рассмотрим содержимое файла хаc, который должен включать последние две строки:

$ cat xac

this ls line5

this ls line6

 

11.7. Заключение

В настоящей главе были рассмотрены различные стандартные утилиты (sort, unique, join, cut, paste и split, а также head и tail), имеющие отношение, главным образом, к сортировке, разделению и объединению текстовых файлов. Применение каждой из них иллюстрировалось многочисленными примерами, которые позволят вам сформировать четкое представление о возможностях этих утилит. Я надеюсь, что благодаря изложенным сведениям вы смогли пополнить свой багаж знаний об инструментах работы с текстом, имеющихся в UNIX и Linux.

 

ГЛАВА 12 Утилита tr

 

12.1. Применение утилиты tr

 

Утилита tr выполняет символьное преобразование путем подстановки или удаления символов из стандартного входного потока. Она часто применяется для удаления управляющих символов из файла или преобразования регистра символов. Как правило, утилите tr передаются две строки: первая строка содержит искомые символы, а вторая — те, на которые их следует заменить. При запуске команды устанавливается соответствие между символами обеих строк, а затем начинается преобразование.

В этой главе рассматриваются следующие темы:

   • преобразование строчных символов в прописные;

   • очистка содержимого файлов от управляющих символов;

   • удаление пустых строк.

Формат утилиты tr с наиболее часто применяемыми параметрами таков:

tr -c -d -s ["строка1"] ["строка2"] входной_файл

где

-c Задает замену набора символов, указанных в строке1 их собственным дополнением при условии, что значение этих символов находится в диапазоне значений кодов ASCII -d Задает удаление во входном файле всех символов, указанных в строке1

-s Задает удаление в последовательности повторяющихся символов всех символов, кроме первого, благодаря чему удаляются повторяющиеся символы

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

 

12.1.1. Диапазоны символов

При использовании утилиты tr можно указать диапазоны или списки символов в виде шаблонов, которые образованы строками. Эти шаблоны подобны регулярным выражениям, однако на самом деле они таковыми не являются. При указании в утилите tr содержимого строк строка1 или строка2 используются только диапазоны и последовательности символов либо отдельные символы.

[a‑z] Строка символов, находящихся в диапазоне a‑z
[A‑Z] Строка символов, находящихся в диапазоне A‑Z
[0-9] Строка чисел
/octal Восьмеричное число, состоящее из трех чисел и представляющее любой действительный символ в коде ASCII
[0*n] Означает символ '0', встречающийся столько раз, сколько указывает значение 'n'. Таким образом, [0*2] означает 00, причем в любой строке, включая и 00

В большинстве вариантов утилиты tr поддерживаются классы символов и сокращенная запись управляющих символов. В формат класса символов [:class:] среди прочего входят следующие обозначения: alnum (буквенно–цифровые символы), alpha (буквы), blank (пропуски), upper (прописные буквы), lower (строчные буквы), cntrl (управляющие символы), space (пробелы), digit (цифры), graph (графические символы) и т. д. В табл. 12.1 представлен способ сокращенного представления некоторых наиболее распространенных управляющих символов, используемый вместо восьмеричного их представления в виде трех чисел, которое также приведено в данной таблице.

Таблица 12.1. Различные способы указания управляющих символов в утилите tr

Сокращение Значение Восмеричное значение
Control‑G — звонок \007
\b Control‑H — клавиша возврата на одну позицию \010
\f Control‑L — прокрутка страницы \014
\n Comrol‑J — новая строка \012
\r Control‑M — клавиша возврата каретки \015
\t Control‑I — клавиша табуляции \011
\v Control‑X \030

При замене строки или диапазона символов одним символом следует иметь в виду, что этот символ не указывается в квадратных скобках ([]). В некоторых системах допускается применение квадратных скобок, причем в данном случае можно для указания символа новой строки воспользоваться шаблоном ["\0l2"] или "\012". Утилита tr не предъявляет строгих требований к виду кавычек. Поэтому не следует удивляться, если эта утилита действует даже в том случае, если вместо одинарных кавычек используются двойные кавычки.

Подобно большинству системных инструментальных средств, утилита tr восприимчива к специальным символам. Поэтому если требуется выполнить сопоставление с одним из таких символов, следует предварительно отделить этот символ обратной косой чертой. Например, для указания левой фигурной скобки ({) необходимо ввести \{ для отмены специального значения фигурной скобки.

 

12.1.2. Сохранение выходного результата

Если нужно сохранить полученные результаты, следует переадресовать их в файл. В приведенном ниже примере выходной результат перенаправляется в файл с именем results.txt. В качестве входного используется файл oops.txt.

$ tr -s "[a‑z]" < oops.txt > results.txt

Обратимся к некоторым примерам использования рассматриваемой команды.

 

12.1.3. Устранение повторяющихся символов

Если проанализировать приведенный ниже файл, можно обнаружить некоторые опечатки. Разумеется, допустить опечатки легко — вспомните, сколько раз во время работы в редакторе vi вы случайно нажимали не те клавиши.

$ pg oops.txt

And the cowwwwws went homeeeeeeee Or did theyyyy

Если нужно избавиться от повторяющихся букв или сократить число подобных букв до одной, можно воспользоваться параметром ' -s'. Также можно воспользоваться шаблоном [a‑z], поскольку в данном случае все символы являются буквенными. При этом входной файл перенаправляется команде tr.

$ tr -s "[a‑z]"< oops.txt

And the cows went home Or did they

Все повторяющиеся символы устраняются. При необходимости файл oops.txt можно перенаправить с помощью команды cat. Результат будет тот же.

$ cat oops.txt | tr -s "[a‑z]"

And the cows went home Or did they

 

12.1.4. Удаление пустых строк

Для удаления пустых строк их следует просто "вытеснить" из файла. Ниже приведен файл с именем plane.txt, содержащий ряд пустых строк.

$ pg plane.txt

987932 Spitfire

190992 Lancaster

238991 Typhoon

В данном случае применяется параметр ' -s', который приводит к удалению пустых строк. Также используется восьмеричное значение для символа новой строки \012. Ниже приведена соответствующая команда.

$ tr -s "[\012]" < plane.txt

987932 Spitfire

190992 Lancaster

238991 Typhoon

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

$ tr -s "[\n]" < plane.txt

987932 Spitfire

190992 Lancaster

238991 Typhoon

 

12.1.5. Преобразование прописных букв в строчные

Изменение регистра символов является наряду с процедурой удаления управляющих символов одним из наиболее распространенных случаев применения утилиты tr. Чтобы выполнить подобное преобразование, достаточно указать шаблон строчных букв '[a‑z]' для входных данных и шаблон прописных букв '[A‑Z]' для выходных преобразованных данных.

В первом примере осуществляется передача утилите tr строки, содержащей смешанный набор символов.

$ echo "May Day, May Day, Going Down.." | tr " [a‑z]" "[A‑Z]"

MAY DAY, MAY DAY, GOING DOWN..

С другой стороны, можно воспользоваться классами символов [:lower:] и [:upper:].

$ echo "May Day, May Day, Going Down.." | tr ":lower:" ":upper:"

MAY DAY, MAY DAY, GOING DOWN..

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

cat file‑to‑translate | tr "[A‑Z]" "[a‑z]" > new‑file‑name

где параметр 'файл–для–преобразования' — преобразуемый файл, а 'имя–нового–файла' — имя, которое нужно присвоить новому файлу. Например:

cat myfile | tr "[A‑Z]" "[a‑z]" > lower_myfile

 

12.1.6. Преобразование строчных букв в прописные

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

$ echo "Look for the route, or make the route" | tr "[a‑z]" "[A‑Z]"

LOOK FOR THE ROUTE, OR MAKE THE ROUTE

$ echo "May Day, May Day, Going Down.." | tr ":lower:" ":upper:"

may day, may day, going down..

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

cat file‑to‑translate | tr "[a‑z]" "[A‑Z]" > new‑file‑name

где 'файл–для–преобразования' - преобразуемый файл, а 'имя–нового–файла' — имя, которое нужно присвоить новому файлу. Например:

cat myfile | tr "[a‑z]" "[A‑Z]" > upper_myfile

 

12.1.7. Удаление определенных символов

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

Приведенный ниже файл содержит часть составленного на неделю личного календаря. Задача заключается в устранении чисел. В личном календаре остаются в наличии только дни недели. Поскольку дни недели используются в формате как прописных, так и строчных букв, в этом случае применяют оба диапазона символов: [a‑z] и [A‑z]. При этом команда 'tr ‑cs "[a‑z] [A‑z]" "[\012*]"' выбирает все содержимое файла, которое не находится в пределах [a‑z] или [A-Z] (буквенных символов), содержащихся в строка1, и преобразует их в символы новой строки. В приведенной выше команде tr параметр ' -s' сообщает о необходимости "сокращения" всех символов новой строки, а параметр '-с' сохраняет без изменения все буквенные символы. Ниже приведен файл, содержащий данные личного календаря, после чего следует строка с утилитой tr и результат ее выполнения.

$ pg diary.txt

Monday 10:50

Tuesday 15:30

Wednesday 15:30

Thursday 10:30

Friday 09.20

$ tr -cs "[a‑z][A‑Z]""[\Q12*]" < diary.txt

Monday

Tuesday

Wednesday

Thursday

Friday

 

12.1.8. Преобразование управляющих символов

Чаще всего утилита tr применяется для преобразования управляющих символов, особенно во время загрузки файлов из DOS в UNIX. Если в команде ftp не задан параметр, выполняющий преобразование символов возврата каретки в символы новой строки, обычно применяют утилиту tr.

Ниже приведен текстовый файл, при пересылке которого не было выполнено преобразование символов возврата каретки. Файл содержит часть требования на выдачу канцелярских принадлежностей. Управляющие символы файла отображены ниже с помощью команды cat -v.

$ cat -v stat.txt

Boxes рарег^^^^^^12^M

Clips metal^^^^^^^50^M

Pencils‑medium^^^^^^10^M ^Z

В этом файле последовательность символов '^^^^^^' кодирует символы табуляции, каждая строка завершается управляющей последовательностью control‑m, а в конце файла находится управляющая последовательность Control‑Z. Ниже показано, как можно исправить положение.

В данном случае придется воспользоваться параметром '~s'. Если обратиться к таблице кодов ASCII, то восьмеричный код символа '^' равен 136. Соответствующее значение для управляющей последовательности ^M равно '015', для символа табуляции — '011', а для управляющей последовательности ^Z — '032'. Данная задача выполняется поэтапно.

Для замены в рассматриваемой команде последовательности символов '^^^^^^' символами табуляции используется следующий шаблон: "\136" "[\011*]". Затем полученные результаты перенаправляются во временный рабочий файл с именем stat.tmp.

$ tr -s '[\136]" "[\011*]" < stat.tr > stat.tmp

Boxes paper 12^M

Clips metal 50^M.

Pencils‑medium 10^М

^Z

Для замены управляющих последовательностей ^M, расположенных в конце каждой строки, символом новой строки и устранения управляющей последовательности ^Z применяется шаблон \n. Не следует забывать, что входные данные поступают из временного файла stat.tmp.

$ tr -s "[\015\032]" "\n" < stat.tmp

Boxes paper 12

Clips metal 50

Pencils‑medium 10

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

 

12.1.9. Быстрые преобразования

Если из файла необходимо удалить только управляющие последовательности ^M и заменить их символами новой строки, для этого применяется команда:

$ tr -s "[\015]" "\n" < файл_ввода

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

$ tr -s "[\r]" "[\n]" < файл_ввода

То же самое преобразование можно выполнить и с помощью команды:

$ tr -s "\r" "\n" < файл_ввода

Еще один распространенный вариант преобразования файлов, перенесенных из DOS в UNIX, иллюстрирует команда:

$ tr -s "[\015\032]" "[\012*]" < файл_ввода

Эта команда удаляет управляющие последовательности ^M и ^Z и заменяет их символами новой строки.

Следующая команда удаляет символы табуляции, заменяя их пробелами:

$ tr -s "[\011]" "[\040*]" < файл_ввода

Для замены в файле пароля passwd всех двоеточий символами табуляции, двоеточие следует заключить в кавычки и указать в строке замены восьмеричное значение символа табуляции, которое равно '011'. Файл станет более удобным для чтения. Сначала приводится файл passwd, а затем команда с утилитой tr, которая выполняет задачу.

$ pg passwd

halt:*:7:0:halt:/sbin:/sbin/halt

mail:*:8:12.mail:/var/spool/mail:

news:*:9:13:news:/var/spool/news:

uucp:*:10:14:tmcp:/var/spool/uucp:

$ tr -s "[:]" "[\011]" < passwd

halt * 7 0 halt /sbin /sbin/halt
mail * 8 12 mail /var/spool/mail
news * 9 13 news /var/spool/news
uucp * 10 14 uucp /var/spool/uucp

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

$ tr "[:]" "[\t]" < passwd

 

12.1.10. Сравнение с несколькими символами

Для выполнения сравнения с несколькими символами применяется формат [character*n]. Ниже приводится содержимое файла, описывающего жесткие диски системы. В файле содержатся диски, которые зарегистрированы или распознаны системой. Первый столбец содержит числа. Если этот столбец не состоит из одних нулей, то регистрируется соответствующий диск во втором столбце.

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

$ pg hdisk.txt

1293 hdisk3

4512 hdisk12

0000 hdisk5

4993 hdisk12

2994 hdisk7

При просмотре файла становится ясно, что имеется один жесткий диск, который не зарегистрирован. Для замены всех нулей, допустим, звездочками можно воспользоваться шаблоном [0*4], который означает поиск соответствия, по крайней мере, четырем нулям. При этом строка замены содержит только звездочки. Ниже приводится соответствующая команда и результат выполнения подобной фильтрации:

$ tr "[0*4]" "*" < hdisk.txt

1293 hdisk3

4512 hdisk12

**** hdisk5

4993 hdisk12

2994 hdisk7

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

 

12.2. Заключение

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