sed — потоковый редактор для фильтрации и преобразования текста
Имя sed — это сокращенное словосочетание stream editor (потоковый редактор). Данная команда осуществляет редактирование потока текста, получаемого из множества файлов или подаваемого на стандартный ввод команды. sed — мощная и достаточно сложная программа (ей посвящены целые книги), поэтому здесь мы не будем рассматривать ее во всех подробностях.
В общем случае sed используется следующим образом: ей передается единственная команда редактирования (в командной строке) или имя файла сценария с множеством команд, и она применяет эти команды к каждой строке в потоке текста. Ниже приводится очень простой пример sed в действии:
[me@linuxbox ~]$ echo "front" | sed 's/front/back/'
back
В этом примере с помощью echo создается поток текста с единственным словом, который по конвейеру передается программе sed. sed, в свою очередь, применяет инструкцию s/front/back/ к тексту в потоке и выводит результат. Эта команда напоминает команду подстановки (поиск с заменой) в редакторе vi.
Команды sed начинаются с единственной буквы. В примере, рассмотренном выше, буква s представляет команду подстановки (substitution). За ней следуют искомая строка и строка замены, разделенные слешем. В качестве разделителя можно использовать любые символы. По общепринятому соглашению, чаще других используется символ слеша, но sed будет использовать в качестве разделителя любой символ, следующий сразу за командой. Ту же самую команду можно было бы записать иначе:
[me@linuxbox ~]$ echo "front" | sed 's_front_back_'
back
Символ подчеркивания, следующий сразу за командой, становится разделителем. Возможность употребления произвольных разделителей можно использовать для улучшения читаемости команд, как будет показано далее.
Большинству команд в sed может предшествовать адрес, который определяет, какие строки во входном потоке должны редактироваться. Если адрес отсутствует, команда редактирования применяется ко всем строкам во входном потоке. В простейшем случае адрес — это номер строки. Мы могли бы добавить единицу в наш пример:
[me@linuxbox ~]$ echo "front" | sed '1s/front/back/'
back
Добавление адреса 1 в команду гарантирует применение операции подстановки только к первой строке в нашем однострочном потоке. Можно указать другое число:
[me@linuxbox ~]$ echo "front" | sed '2s/front/back/'
front
Теперь, как видите, редактирование не было выполнено, потому что во входном потоке отсутствует строка с номером 2.
Адреса можно выражать множеством способов. В табл. 20.7 перечислены адреса, чаще других используемые на практике.
Таблица 20.7. Форма записи адресов в команде sed
Адрес
Описание
n
Номер строки, где n — положительное число
$
Последняя строка
/регулярное_выражение/
Строки, соответствующие простому регулярному выражению POSIX. Обратите внимание, что регулярное выражение должно ограничиваться символом слеша с обеих сторон. При желании можно использовать другие ограничительные символы, определив регулярное выражение в форме cрегулярное_выражениеc, где c — альтернативный символ-ограничитель
адр1,адр2
Диапазон строк с номерами от адр1 по адр2 включительно. Каждый адрес может иметь любую форму из перечисленных выше
первая~шаг
Соответствует строке с номером первая и каждой последующей с указанным шагом. Например, адрес 1~2 соответствует всем строкам с нечетными номерами, а адрес 5~5 соответствует пятой строке и каждой пятой последующей
адр1,+n
Соответствует строке с адресом адр1 и следующим за ней n строкам
adr!
Соответствует всем строкам, кроме строки с адресом адр, где адрес может иметь любую форму из перечисленных выше
Рассмотрим разные способы адресации строк на примере файла distros.txt, созданного выше в этой главе. Сначала попробуем диапазоны номеров строк:
[me@linuxbox ~]$ sed -n '1,5p' distros.txt
SUSE 10.2 12/07/2006
Fedora 10 11/25/2008
SUSE 11.0 06/19/2008
Ubuntu 8.04 04/24/2008
Fedora 8 11/08/2007
В нашем примере мы вывели строки с 1 по 5. Для этого использовалась команда p, которая просто выводит строки, соответствующие адресам. Однако здесь нам пришлось добавить параметр -n (параметр подавления автоматического вывода), чтобы программа sed не выводила все строки, что она делает по умолчанию.
Далее попробуем задействовать регулярное выражение:
[me@linuxbox ~]$ sed -n '/SUSE/p' distros.txt
SUSE 10.2 12/07/2006
SUSE 11.0 06/19/2008
SUSE 10.3 10/04/2007
SUSE 10.1 05/11/2006
Включив регулярное выражение /SUSE/, заключенное в символы слеша, мы смогли выделить строки подобно тому, как это делает программа grep.
Наконец, попробуем применить оператор отрицания, добавив в адрес восклицательный знак (!):
[me@linuxbox ~]$ sed -n '/SUSE/!p' distros.txt
Fedora 10 11/25/2008
Ubuntu 8.04 04/24/2008
Fedora 8 11/08/2007
Ubuntu 6.10 10/26/2006
Fedora 7 05/31/2007
Ubuntu 7.10 10/18/2007
Ubuntu 7.04 04/19/2007
Fedora 6 10/24/2006
Fedora 9 05/13/2008
Ubuntu 6.06 06/01/2006
Ubuntu 8.10 10/30/2008
Fedora 5 03/20/2006
Здесь мы видим ожидаемый результат: все строки из файла, кроме совпавших с регулярным выражением.
Пока что мы познакомились лишь с двумя командами редактирования, поддерживаемыми программой sed, s и p. В табл. 20.8 приводится более полный список основных команд редактирования.
Таблица 20.8. Основные команды редактирования sed
Команда
Описание
=
Выводит номер текущей строки
a
Добавляет текст в конец текущей строки
d
Удаляет текущую строку
i
Вставляет текст в начало текущей строки
p
Выводит текущую строку. По умолчанию sed выводит все строки, но редактирует только соответствующие указанному адресу. Поведение по умолчанию можно отменить, передав параметр -n
q
Завершает sed без обработки остальных строк. Если параметр -n не указан, выводит текущую строку
Q
Завершает sed без обработки остальных строк
s/регулярное_выражение/строка_замены/
Замещает совпадение с регулярным выражением строкой замены. Строка замены может включать специальный символ &, обозначающий совпадение с регулярным выражением. Кроме того, строка замены может включать последовательности, с 1 по 9, обозначающие совпадения с соответствующими подвыражениями в регулярном выражении. Дополнительную информацию по этой теме можно найти в обсуждении обратных ссылок ниже. За символом слеша, закрывающим строку замены, может следовать необязательный флаг, определяющий дополнительные особенности поведения команды
y/множество1/множество2
Выполняет перекодирование, преобразуя символы из первого множества в символы второго множества. Имейте в виду, что, в отличие от программы tr, sed требует, чтобы оба множества были одинаковой длины
Команда s, вне всяких сомнений, используется намного чаще других команд редактирования. Далее мы рассмотрим только часть ее возможностей, выполняя редактирование нашего файла distros.txt. Мы уже говорили, что поле даты в distros.txt хранит информацию не в самом «дружественном» для компьютеров виде. Здесь даты записаны в формате ММ/ДД/ГГГГ, однако гораздо удобнее (для сортировки) было бы, если бы даты были записаны в формате ГГГГ-ММ-ДД. Замена представления дат вручную — довольно утомительное занятие и чревато ошибками, но с помощью sed ту же замену можно выполнить в одно действие:
[me@linuxbox ~]$ sed 's/([0-9]{2})/([0-9]{2})/([0-9]{4})$/3-1-2/' distros.txt
SUSE 10.2 2006-12-07
Fedora 10 2008-11-25
SUSE 11.0 2008-06-19
Ubuntu 8.04 2008-04-24
Fedora 8 2007-11-08
SUSE 10.3 2007-10-04
Ubuntu 6.10 2006-10-26
Fedora 7 2007-05-31
Ubuntu 7.10 2007-10-18
Ubuntu 7.04 2007-04-19
SUSE 10.1 2006-05-11
Fedora 6 2006-10-24
Fedora 9 2008-05-13
Ubuntu 6.06 2006-06-01
Ubuntu 8.10 2008-10-30
Fedora 5 2006-03-20
Прекрасный результат! Правда, команда выглядит устрашающе, но она работает. За один шаг мы изменили представление дат во всем файле. Этот пример также наглядно показывает, почему про регулярные выражения иногда в шутку говорят «только для записи». Мы можем писать их, но прочитать их порой никак не получается. Прежде чем сбежать от этой устрашающей команды, давайте посмотрим, как она была сконструирована. Во-первых, как мы уже знаем, эта команда имеет следующую структуру:
sed 's/регулярное_выражение/строка_замены/' distros.txt
Теперь разберем регулярное выражение, отыскивающее даты. Так как даты имеют формат ММ/ДД/ГГГГ и находятся в конце строки, найти их можно с помощью следующего выражения:
[0-9]{2}/[0-9]{2}/[0-9]{4}$
которому соответствуют две цифры, слеш, две цифры, слеш, четыре цифры и конец строки. Так, с регулярным выражением разобрались, а что со строкой замены? Чтобы описать ее, нам необходимо познакомиться с новой для нас особенностью регулярных выражений, которую можно использовать в некоторых приложениях, поддерживающих BRE. Эта особенность называется обратные ссылки, и действует она так: если в строке замены присутствует последовательность , где n — число от одного до девяти, эта последовательность будет ссылаться на совпадение с соответствующим подвыражением в предшествующем регулярном выражении. Чтобы создать подвыражение, достаточно просто заключить часть регулярного выражения в круглые скобки, например:
([0-9]{2})/([0-9]{2})/([0-9]{4})$
Теперь у нас есть три подвыражения. Первому соответствует месяц, второму — число месяца и третьему — год. Соответственно строку замены можно выразить так:
3-1-2
что даст нам в результате такую последовательность: год, дефис, месяц, дефис, число месяца.
Теперь наша команда приобрела следующий вид:
sed 's/([0-9]{2})/([0-9]{2})/([0-9]{4})$/3-1-2/' distros.txt
Но остались две проблемы. Первая: дополнительные слеши в регулярном выражении запутают программу sed, когда она попытается интерпретировать команду s. Вторая: так как по умолчанию sed принимает только простые регулярные выражения, некоторые символы в нашем регулярном выражении будут интерпретироваться как литералы, а не как метасимволы. Мы решим обе проблемы, применив символы обратного слеша для экранирования нужных символов:
sed 's/([0-9]{2})/([0-9]{2})/([0-9]{4})$/3-1-2/' distros.txt
И дело в шляпе!
Другая особенность команды s — возможность использования дополнительных флагов вслед за строкой замены. Наиболее примечательным из них является флаг g, который требует от sed применить поиск с заменой к строке глобально (globally), а не только к первому найденному совпадению, как это делается по умолчанию.
Например:
[me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/'
aaaBbbccc
Как видите, замена была выполнена только для первого вхождения буквы b, а остальные остались нетронутыми. Добавив флаг g, можно изменить все вхождения:
[me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/g'
aaaBBBccc
До сих пор мы передавали команды программе sed только по одной и только в командной строке. Однако существует возможность создавать более сложные команды в файлах сценариев и передавать эти сценарии с помощью параметра -f. Для демонстрации создадим с помощью sed отчет на основе нашего файла distros.txt. Отчет будет содержать заголовок вверху, измененные даты и названия дистрибутивов будут преобразованы в верхний регистр. Для этого нам понадобится написать сценарий, поэтому запустите текстовый редактор и введите следующие строки:
# Сценарий для sed, создающий отчет о дистрибутивах Linux
1 i
Linux Distributions Report
s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/3-1-2/
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
Сохраните сценарий в файл с именем distros.sed и запустите его:
[me@linuxbox ~]$ sed -f distros.sed distros.txt
Linux Distributions Report
SUSE 10.2 2006-12-07
FEDORA 10 2008-11-25
SUSE 11.0 2008-06-19
UBUNTU 8.04 2008-04-24
FEDORA 8 2007-11-08
SUSE 10.3 2007-10-04
UBUNTU 6.10 2006-10-26
FEDORA 7 2007-05-31
UBUNTU 7.10 2007-10-18
UBUNTU 7.04 2007-04-19
SUSE 10.1 2006-05-11
FEDORA 6 2006-10-24
FEDORA 9 2008-05-13
UBUNTU 6.06 2006-06-01
UBUNTU 8.10 2008-10-30
FEDORA 5 2006-03-20
Как видите, сценарий выдал желаемый результат, но как он это сделал? Давайте вернемся еще раз к нашему сценарию. Выведем его с помощью программы cat так, чтобы она пронумеровала строки.
[me@linuxbox ~]$ cat -n distros.sed
1 # Сценарий для sed, создающий отчет о дистрибутивах Linux
2
3 1 i
4
5 Linux Distributions Report
6
7 s/([0-9]{2})/([0-9]{2})/([0-9]{4})$/3-1-2/
8 y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
Строка с номером 1 — это комментарий. Так же как во многих конфигурационных файлах и языках программирования, широко используемых в Linux, комментарии начинаются с символа #, за которым следует пояснительный текст. Комментарии можно помещать в сценарии в любое место (только не в сами команды), и они, безусловно, полезны для всех, кто хочет выяснить особенности работы сценария или сопровождать его.
Строка 2 — это пустая строка. Так же как комментарии, мы можем добавлять пустые строки для удобочитаемости.
Многие команды sed поддерживают адресацию строк. Адреса используются, чтобы определить, к каким строкам во входном потоке применяется операция. Адреса могут выражаться в форме простых номеров строк, диапазонов номеров и специального номера $, соответствующего последней входной строке.
В строках с 3-й по 6-ю содержится текст, который должен быть вставлен по адресу 1, в первую входную строку. Команда i, сопровождаемая последовательностью из обратного слеша и перевода строки, производит экранированный символ перевода, или то, что мы называем символом продолжения строки. Эта последовательность используется во многих случаях, в том числе и в сценариях на языке командной оболочки, позволяя встраивать символ перевода строки в поток текста так, чтобы он не воспринимался интерпретатором (в данном случае программой sed) как конец строки. Команда i, а также команда a (добавления текста в конец) и команда c (замены текста), могут располагаться в нескольких строках текста, при условии, что каждая из них, кроме последней, будет завершаться символом продолжения строки. Шестая строка в нашем сценарии фактически завершает вставляемый текст и заканчивается уже не символом продолжения строки, а простым переводом строки, сообщая о завершении команды i.
ПРИМЕЧАНИЕ
Символ продолжения строки формируется из обратного слеша и следующего сразу за ним перевода строки. Никаких промежуточных пробелов между ними быть не должно.
Строка 7 — наша команда поиска с заменой. Так как ей не предшествует никакой конкретный адрес, она будет выполнена для каждой строки во входном потоке.
Строка 8 выполняет перекодировку букв нижнего регистра в буквы верхнего регистра. Обратите внимание, что, в отличие от программы tr, команда y в sed не поддерживает ни диапазоны символов (например, [a-z]), ни классы символов POSIX. И снова, так как команде y не предшествует никакой конкретный адрес, она будет выполнена для каждой строки во входном потоке.
использующие sed также часто выбирают...
Программа sed обладает очень широкими возможностями. С ее помощью можно решать весьма сложные задачи, связанные с редактированием потока текста. Но чаще она используется для выполнения простеньких операций, определение которых укладывается в одну строку. Для решения объемных задач многие предпочитают использовать другие инструменты. Наиболее популярными из них являются awk и perl. Они не относятся к разряду простых инструментов, как программы, обсуждаемые здесь, а являются полноценными языками программирования. perl, например, часто применяется взамен языка командной оболочки для решения многих задач системного администрирования, а также пользуется большой популярностью как средство разработки веб-приложений. awk имеет более узкую область применения. Основное его достоинство заключается в возможности управления табличными данными. Он напоминает sed в том смысле, что программы на awk обычно занимаются построчной обработкой текстовых файлов, используя схему, похожую на адреса в sed со следующими за ними операциями. Даже при том, что обсуждение awk и perl выходит за рамки этой книги, они являются отличными инструментами для пользователей командной строки в Linux.