Программирование в sed.

Вступление.

sed программы содержат одну или более sed команд. Программы записываются в файл, который выполняется командой sed с ключом -f, либо в командной строке первым параметром (без «-» вначале), либо после ключа -e.

Некоторые sed-команды содержат адрес, либо диапазон адресов. Команды sed записываются одним символом, но могут содержать дополнительные параметры и флаги.

Выбор строк, которые редактирует `sed' (адресация).

Адресация в sed имеет следующие формы:

NUMBER
Номер строки. Целое, неотрицательное число большее нуля. С этим адресом совпадает только одна строка с номером NUMBER.

Замечание

Если используется ключ -s или -i, то с этим адресом совпадают все строки с номером NUMBER для каждого файла.
FIRST~STEP
Это GNU расширение адресации совпадает с каждой строкой начиная с FIRST, с шагом STEP. STEP должен быть неотрицательным числом. Например, "1~2" совпадает со всеми нечётными строками, "2~2" со всеми чётными (можно использовать "0~2" с тем-же результатом) "3~5" со строками 3,8,13,18...3+5*n "10~0" совпадает только с десятой строкой. "10~1" совпадает со всеми строками, начиная с десятой.
$
Этот адрес совпадает с последней строкой входного потока, или с последними строками каждого входного файла при использовании опций -i или -s.
/REGEXP/

Выбирает только строки, в которых встречается выражение /REGEXP/ , если ваше выражение содержит «/», вы их должны экранировать обратным слешем «\».

Если «POSIXLY_CORRECT» не определена, то пустое выражение // совпадает с последним найденным выражением команды s///.

Замечание

Скомпилированое регулярное выражение может изменится в процессе выполнения скрипта.

Если переменная «POSIXLY_CORRECT» установлена, то пустое регулярное выражение ни с чем не совпадает. Такое поведение определено стандартом POSIX.

\%REGEXP%

(Вместо % можно использовать любой другой символ)

Так-же как и /REGEXP/, но вместо разделителя(слеша) используется %(либо другой символ), если в выражении присутствует разделитель, его необходимо экранировать.

/REGEXP/ I \%REGEXP%I
Модификатор I используется для того, что-бы выражение было нечувствительно к регистру. (это GNU расширение).
/REGEXP/ M \%REGEXP%M

Этот модификатор так-же является GNU расширением.

Он позволяет строить регулярные выражения в многострочном режиме. Обычно в буфере находится всего одна строка(особенно при использовании адресации), и этот модификатор ни на что не влияет. Его влияние заметно только в том случае, если в буфере sed находится сразу несколько строк(это может произойти после применения команд N, G, либо если в буфер была записана область удержания, к которой была применена команда H).

Например: содержимое буфера:

abc\n def\n

(«\n» означает перевод строки)

выражение /f$/ совпадает и с модификатором M, и без него, но выражение /c$/ совпадает только с модификатором: /c$/ M, потому-что по умолчанию, sed считает буфер одной строкой, даже если в нём присутствуют символы новой строки.

Если нет адреса, sed обрабатывает все строки.

Если имеется один адрес, sed обрабатывает только строки, которые совпадают с этим адресом.

Если имеются два адреса разделённые запятой(«,»), sed обрабатывает диапазон адресов, с первого адреса, до второго(включительно). Если второй адрес является регулярным выражением, тогда его совпадение проверяется только начиная со следующей строки, после той, с которой совпал первый адрес. Если вы хотите проверять текст с первой строки, но так, что-бы условие окончания диапазона проверялось и в первой строке тоже, используйте GNU расширение:

0, /REGEXP/

такая форма адресации совпадает только с первой строкой, если эта строка совпадает с /REGEXP/ . Обычная адресация(1, /REGEXP/ ) совпадает со всеми строками текста, или до совпадения какой-либо строки с /REGEXP/ , начиная со второй.

Если второй адрес является числом, и оно меньше номера строки совпавшей с первым адресом (или равно ему), то выводится только эта строка.

GNU sed поддерживает следующие расширения адресации:

0,ADDR2
Совпадает со всеми строками, начиная с первой, и до ADDR2. Работает так-же как 1,ADDR2, если первая строка не совпадает с ADDR2, Если первая строка совпадает с ADDR2, то этот диапазон совпадает только с ней(первой строкой).
ADDR1,+N
Совпадает с N+1 строками подряд, начиная с ADDR1.
ADDR1,~N
Совпадает со строками начиная с ADDR1, и до строки, которая делится на N.

Добавление символа «!» в конце адресного выражения приводит к его инверсии, с таким выражением совпадает всё, что не совпадает с адресом. Если с адресным выражением совпадают все строки(например его нет), то добавление ! Приводит к тому, что с таким адресом ничего не совпадает.

Обзор синтаксиса регулярных выражений.

Для того, что-бы использовать sed, необходимо понимать регулярные выражения ( regexp(RE или для расширенных ERE), для краткости). Регулярное выражение, это шаблон, который проверяется на совпадение с заданной строкой (другими словами, шаблон ищется в данной строке). Проверка производится слева направо.

Многие символы в regexp воспринимаются "как есть": Если мы желаем найти в строке символ «Q», то для поиска потребуется regexp /Q/ . Тривиальный пример: этот regexp - «test» совпадает с теми, и только с теми строками, в которых есть слово «test».

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

СИМВОЛ
Совпадает с одним СИМВОЛом.
«*»

Совпадает любым числом вхождений предыдущего символа или предыдущего регулярного подвыражения(в том числе и его отсутствия). Если вы желаете найти именно символ «*», то его следует за экранировать («\*»).

Расширение GNU: «a**» эквивалентно «a*». Правда это не стандартно, и применять такие конструкции не следует (они могут быть восприняты как «a*\*»).

«+»
Так-же как «*», однако хотя-бы один символ должен присутствовать (GNU расширение).
«?»
Так-же как «*», но совпадает только с 0 либо с 1 символом (GNU расширение).
{I}
Совпадает точно с I символами, например /X\{5\}/ совпадает только с XXXXX. (I - целое десятичное число, для переносимости должно быть в диапазоне 0...255).
{I,J}
Диапазон числа совпадений I...J
{I,}
Число совпадений не менее I.
(REGEXP)
Группирование выражений. Применяется в следующих случаях:
  1. Для применения постфиксных операций. Например:

    /(XY){3}/

    совпадает с XYXYXY, но

    /XY{3}/

    совпадает только с XYYY.

    Замечание

    Стандарт POSIX 1003.1-2001 требует такого поведения, и многие non-GNU реализации его поддерживают, однако такая конструкция в общем случае не переносима.

  2. Для реализации обратных ссылок(см. ниже).
«.» (точка)
Совпадает с любым одиночным символом, включая символ перевода строки.

Замечание

Однако, см.выше, Ограничения sed, пункт 1.б. (примечание drBatty)
Что такое "Любой одиночный символ"? Это лучше пояснить примером
sed 's/./Z/'
(описание sed-команды s см.ниже, в данном случае sed просто поменяет шаблон /./ на "Z").
$ echo "A" | sed 's/./Z/'
	Z
	# самое простое - я дал sed один символ, и она его поменяла.
	$ echo "ABCDE" | sed 's/./Z/'
	ZBCDE
	# в данной строке меняется только первый символ(с помощью модификаторов s (см. ниже)
	# это поведение можно изменить)
	$ echo "" | sed 's/./Z/'
	# пустая строка не изменилась, в этом случае выполнение команды s не так просто -
	# дело в том, что я просил "найти любой символ", но она не смогла этого сделать
	# (т.к. символов вообще нет), потому замены не было, и строка осталась без
	# изменения.
«^»
Неплохо сказано в оригинале: «Matches the null string at beginning of line» Уж и не знаю, как это перевести :-( Попытаюсь объяснить своими словами: «^» является "псевдосимволом", т.е. одним из специальных символов, которые не совпадают ни с каким из символов в оригинальной строке, однако совпадают с определённой ПОЗИЦИЕЙ в строке. Этот символ ( «^» ) совпадает с началом строки, Замена этого символа на строку «XXX» равносильна тому, что перед исходной строкой вставляется «XXX». Как справедливо сказано в оригинале(который я выше процитировал), сам символ «^» соответствует пустой строке. В этом можно убедится так: поменяем всю строку на содержимое этого символа(здесь я использую обратные ссылки, о них ниже)
$ echo 'ABCD' | sed -r 's/(^).*/\1/'
# ответ - пустая строка. Здесь я заменил всю строку на внутренность в скобках (^)
В оригинале есть и такой пример:
/^#include/
это выражение отыщет все строки, которые начинаются на «#include», те строки которые не начинаются на «#include» будут проигнорированы, даже если перед «#include» стоят только пробелы. Для совместимости, не следует использовать «^» в начале подвыражений, так-как POSIX стандарт трактует этот символ как обычный в данном контексте.

Замечание

(drBatty):

Моя версия(4.0.9), похоже всегда считает этот символ специальным, даже тогда, когда использование его как спецсимвол не имеет смысла (например /.^/ ). Потому, если вы хотите искать именно «^», в RE его необходимо экранировать. Кроме того, тут почему-то не сказано про "многострочный" режим(модификаторы I, для адресного RE, и m для команды s (об этом сказано чуть выше)).

«$»
Так-же как «^», но совпадает с концом строки. Например для поиска пустых строк используйте шаблон /^$/ , под него попадают только строки, в которых ничего нет, даже пробелов.
[СПИСОК] [^СПИСОК]

Совпадает с единственным символом из СПИСКА. Для примера, RE

/[aeiou]/

совпадает только с одной из маленьких гласных нерусских букв. Список может включать диапазоны символов, например /[0-9]/ совпадает с любой цифрой, возможны и более сложные конструкции, например /[0-9A-Fa-f]/ совпадает с любой шестнадцатеричной цифрой. Если перед СПИСКОМ стоит «^», то СПИСОК инвертируется, он совпадёт с любым символом не из этого СПИСКА.

Подсказка

Если вы хотите включить в СПИСОК«]», поместите его в начало списка (после «^», если необходимо), если нужно включить «-», поместите его в начало или в конец списка. Для включения «^» поместите его в любое место СПИСКА, кроме первой позиции.

Символы «$», «*», «.», «[», и «\» не имеют специального значения внутри СПИСКА. Для примера, /[\*]/ совпадает с «\» или с «*», потому-что «\» не имеет специального значения в этом контексте. А дальше фигня какая-то написана про специальные символьные классы(выше я писал про обычные [СПИСОК] и [^СПИСОК]), приведу без перевода:

However, strings like `[.ch.]', `[=a=]', and `[:space:]' are special within LIST and represent collating symbols, equivalence classes, and character classes, respectively, and `[' is therefore special within LIST when it is followed by `.', `=', or `:'. Special escapes like `\n' and `\t' are recognized within LIST; this will change in a future version in `POSIXLY_CORRECT' mode. *Note Escapes::

На самом деле, «\n» и «\t» внутри списка действительно могут присутствовать (в т.ч. и некоторые другие, например «\a»), а что касается спец-классов, то тут как раз тот редкий случай, когда лучше почитать ВАШ man 7 regex, там более подробная и правильная информация. Которая, впрочем может тоже не подойти к ВАШЕЙ реализации sed(особенно, если вы взяли свою sed откуда-то слева) например у меня поддерживаются следующие классы(хотя я не все проверял)

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

На самом деле, я недолюбливаю эти спец-классы: никогда точно не знаешь, какие именно символы данная конкретная реализация считает непечатными, и какие именно символы считаются большими буквами(вопрос совсем не тривиальный, если буквы русские). ИМХО в правильно настроенной системе текст в «правильной» кодировке будет корректно обрабатываться командой sed. Однако... Проблема в том, что за кодировки отвечает несколько переменных, и они могут иметь различное значение, потому команда "iconv -f КОДИРОВКА" должна вообще-то преобразовать из какой-то КОДИРОВКИ в родную, которая затем должна быть нормально и читаемо вывестись на экран, при этом, я могу с помощью sed скажем заменить все большие русские буквы на малые, и это всё должно быть нормально видно... Но... Я предпочитаю написать просто [а-яА-Я] для поиска любой русской буквы, чем потом удивляться.

Важно

...Причём стоит заметить, что выражние /[а-яА-Я]/ совпадает с русскими буквами только в кодировке cp1251.

(кстати, не все русские кодировки поддерживают такие диапазоны - к сожалению, в некоторых есть "дыры", т.е. [а-я] включает НЕ ТОЛЬКО русские буквы). См. также.

Замечание

(drBatty) А вообще в большинстве систем sed вовсе и не включает в себя регулярные выражения - она использует системные, которые обычно имеются в glibc. Тащить за собой RE приходится только в "недосистемах" вроде M$ Windows. Именно по этой причине, тонкости работы RE в sed следует выяснять изучая исходники и мануалы к glibc, а вовсе не к sed. См. например уже упомянутый man regex.

REGEXP1|REGEXP2
Это выражение совпадает с REGEXP1 или с REGEXP2. Исходная строка просматривается слева на право, и совпадением считается самое левое вхождение одного из шаблонов. Например:
$ echo 'ABCDEF' | sed -r 's/D|C/W/'
ABWDEF
$ echo 'ABCDEF' | sed -r 's/C|D/W/'
ABWDEF
Как видите, оба шаблона ( /C|D/ и /D|C/ ) находят первую встреченную букву (C в данном случае).

Замечание

drBatty: Я не в курсе, почему в оригинале не рассказано о важном частном случае: если найдено совпадение ОБОИХ альтернатив, в этом случае, моя версия принимает совпадение самого "длинного" шаблона, точнее того, который захватил больше символов:
$ echo 'ABCDEFGH' | sed -r 's/C.+|CDE/W/'
ABW
$ echo 'ABCDEFGH' | sed -r 's/CDEF|C.+/W/'
ABW
Видно, что захватывает символы подвыражение /C.+/ См. также
Это расширение GNU.
REGEXP1REGEXP2
Совпадает, если совпало объединение (конкатенация) REGEXP1 и REGEXP2.
\ЦИФРА
Совпадает с подвыражением с номером ЦИФРА считая с левого края шаблона, "Подвыражением" называется любое RE заключённое в (скобки). \ЦИФРА называется "обратной ссылкой", по той причине, что исходная строка просматривается слева направо и ссылка может указывать только на то, что уже просмотрено (на то что слева) Например:
$ echo 'ABCDEFGH' | sed -r 's/.*\1.*(C).*/FOUND/'
sed: -e выражение #1, символ 20: Invalid back reference
# Ошибка. Обратная ссылка не может указывать на выражение правее её.
$ echo 'ABCDEFCGH' | sed -r 's/.*(.).*\1.*/FOUND/'
FOUND
# Ещё раз, и медленно :-)
# 1) любое количество любых символов
# 2) любой одиночный символ
# 3) любое количество любых символов
# 4) символ, такой-же, как был найден в пункте 2
# 5) любое количество любых символов
# Это очень нужное RE, и оно часто применяется. В данном случае важны пункты
# 2 и 4 - в пункте 2 находится любой символ, а в пункте 4 находится точно такой-же
# т.о. это RE совпадает только со строками в которых есть хотя-бы 2 одинаковых
# символа. Причём не важно, что-это за символы. Кроме того, неважно,что находится
# до, между, и после этих символов(в данном случае).
$ echo 'ABCDEFGH' | sed -r 's/.*(.).*\1.*/FOUND/'
ABCDEFGH
# В данном случае одинаковых символов нет, поэтому строка не меняется.
$ echo 'ABcdEFdGcH' | sed -r 's/.*(.).*\1.*/\1/'
d
$ echo 'ABcdEFGcHd' | sed -r 's/.*(.).*\1.*/\1/'
d
# в отличие от других случаев, обратные ссылки находят не первое, а ПОСЛЕДНЕЕ
# совпадение. Т.е., если у нас есть(как в этом примере)
# cd****строка в которой есть и c и d в любом порядке(хвост)****
# то отыщется именно d.
# однако, это касается только случая, если в (хвосте) нету других одинаковых пар
# если они там есть, результат будет иным:
$ echo 'ABcdEFGccHd' | sed -r 's/.*(.).*\1.*/\1/'
c
$ echo 'ABcdEFGttHd' | sed -r 's/.*(.).*\1.*/\1/'
t

Замечание

(drBatty): Следует учитывать, что обработка обратных ссылок может занять огромное количество времени и оперативной памяти (особенно если строк много и они длинные), связано это с тем, что при использовании обратных ссылок sed запоминает ВСЕ вхождения подвыражения в скобках, на тот случай, если оно опять встретится.
\n
Совпадает с символом новой строки. Обычно такие символы никогда не встречаются в строках, т.к. sed загружает поток построчно.
\СИМВОЛ
Совпадает с СИМВОЛом, если символ, один из «$», «*», «.», «[», «\», или «^».

Замечание

это соответствует только C-подобным конструкциям. «\t» не является переносимой конструкцией, лучше использовать сам символ «\t» в ваших выражениях.
Note that the regular expression matcher is greedy, i.e., if two or more matches are detected, it selects the longest; if there are two or more selected with the same size, it selects the first in text.

Примеры:

/abcdef/

Совпадает с `abcdef'.

/a*b/

Совпадает с нулевым или большим количеством символов «a» после которых следует одиночный символ «b». Для примера, «b» или «aaaaab».

/a?b/

Совпадает с «b» или с «ab».

Замечание

drBatty: и не с чем иным

/a+b+/

Совпадает с одним(или с большим числом) символов «a» после которого идёт один, либо большее число символов «b». «ab» это самое короткое совпадение, другие совпадения: «aaaab» или «abbbbb» или «aaaaaabbbbbbb».

/.*/

/.+/

Оба этих выражения совпадают со всеми символами в строке; первое выражение отличается тем, что оно совпадает в т.ч. и с пустыми строками. Второе выражение совпадает лишь с теми строками, в которых имеется хотя-бы один символ. (см. ограничения sed пункт 1.б)

/^main.*\(.*\)/

Это ERE совпадает с любой строкой, которая начинается на «main», после чего содержит любое количество любых символов.

Замечание

в т.ч. и не содержит, что противоречит синтаксису Си(примечание drBatty)

после чего идёт открывающая скобка «(», ещё некоторое количество символов(или без символов), в затем, закрывающая скобка( «)» ).

/^\#/

Совпадает со строками, которые начинаются на «#».

/\\$/

Это RE совпадает со строками, в конце которых стоит обратный слеш ( «\» ). RE содержит два обратных слеша для экранирования.

/\$/

Это RE совпадает с любой строкой, в которой имеется хотя-бы один символ «$».

/[a-zA-Z0-9]/

В английской локали, это выражение совпадает с любой буквой, либо с любой цифрой.

Замечание

(drBatty): по-русски любая буква или цифра: [a-zA-Z0-9а-яА-ЯёЁ] это выражение действует в большинстве(но не во всех!) кодировках. А точнее - ни в каких не совпадает(кроме cp1251). Правильнее использовать «\w», которое совпадает с любой буквой в текущей кодировке.

/[^ tab]+/

(тут `tab' означает единственный символ табуляции.) Это выражение совпадает с одним, либо большим числом символов, равных пробелу либо табуляции. Эти символы обычно разделяют слова в предложениях.

/^(.*)\n\1$/

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

/.{9}A$/

Это выражение совпадает с девятью любыми символами, после которых стоит символ `A'.

Замечание

(drBatty): это выражение не совпадает с "ыыыыA", по той причине, что перед 'A' стоИт всего 4 символа, а не 9.

/^.{15}A/

Это выражение совпадает с любой строкой, которая длиннее 15и символов, и кроме того, 16м символом должен быть символ 'A'.

Как `sed' буферизирует данные?

`sed' работает с (двумя? неа!) с одним буфером для данных. Второй буфер зарезервирован в большинстве обычных операций, sed читает входной поток в буфер, обрабатывает его, и выводит результат туда-же. В некоторых случаях sed выводит результат в stdout, но НИКОГДА не выводит в "буфер2". Буфер2 изначально инициализирован пустой строкой ("\n"). Он используется для сохранения промежуточных результатов между обработкой строк (если кодеру ЭТО надо). Лишь немногие команды ('h', 'H' и 'x') имеют доступ (для модификации) к "буферу2". Впрочем для чтения из "буфера2" так-же немного команд ('x', 'g', и 'G'). В оригинале, буфер2 называется "hold space".

Замечание

на самом деле, перед обработкой первой строки в буфере2 вообще ничего нет, в т.ч. и символа \n, этот символ добавляется туда командами, например команда `H' сначала добавит к буферу2 `\n', а уж затем добавит буфер.

Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).

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