Неинтерактивный текстовый редактор SED

Все мы привыкли работать в интерактивных текстовых редакторах, при этом обычно используя клавиатуру, мышку или другое устройство ввода. Интерактивные редакторы вначале загружают весь текст документа, а затем применяют к нему команды от пользователя по одной, в то время как «sed» (stream editor) вначале загружает в себя набор команд, а затем применяет весь этот набор команд к каждой строчке текста. Команду «sed» удобно использовать для автоматического редактирования текстовых данных. Чуть ранее мы уже рассмотрели одну замечательную утилиту для фильтрации текста grep. Как «grep», так и «sed» используют регулярные выражения, но «sed» мощнее, поскольку это и потоковый текстовый редактор и Тьюринг-полный язык программирования, настолько полный, что на нём можно, но не нужно, писать даже игры типа шахмат. Выбирая между этими утилитами стоит помнить, что «grep» это просто фильтр, а «sed» это уже полноценный редактор с большими возможностями.

ОБРАЗЕЦ

GIST | Если не передавать «sed» никаких команд редактирования, то его можно использовать в качестве обычной читалки текстовых файлов с выводом на экран. При оформлении этого и последующих примеров будем придерживаться следующего порядка: сверху параметры командной строки, внизу стандартные потоки слева ввода stdin и справа вывода stdout:

''
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809

Команда «sed» имеет не так уж много опций командной строки, которые можно указать при запуске. Из них часто используются далеко не все. Действительно полезными опциями являются возможность включить расширенные регулярные выражения, возможность редактирования файла на месте и возможность подавлять автоматический вывод каждой строки.

ПЕЧАТЬ

GIST | Почему «sed» напечатал каждую строку дважды ? Всё потому, что «sed» работает построчно. Он принимает строку, выполняет поставленную задачу и выводит результат, а затем повторяет процесс для следующей строки. После того, как «sed» получил команду печати «p» (print), он помимо этого ещё вывел каждую строку автоматически:

p
George Washington, 1789-1797
John Adams, 1797-1801
George Washington, 1789-1797
George Washington, 1789-1797
John Adams, 1797-1801
John Adams, 1797-1801

GIST | Чтобы освободить результаты от повторов при использовании команды «p», можно отключить в «sed» автоматический вывод каждой строки:

-n p
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809

Согласен, пока не впечатляет. Мы пришли к тому, с чего начинали в образце, только сложнее. Но дальше будет интересней.

ДИАПАЗОНЫ АДРЕСОВ

GIST | Как насчёт вывода только одной единственной строки ? Чтобы напечатать только первую строку, достаточно указать её номер перед командой «p»:

-n '1p'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, 1789-1797

GIST | Если в команде указан диапазон, то «sed» выполняет все команды только на строках, которые входят в этот диапазон. Следующий пример использует диапазон строк с первой по пятую и применит команду для печати «p» для них:

-n '1,5p'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
John Quincy Adams, 1825-1829
Andrew Jackson, 1829-1837
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825

GIST | Диапазон можно задать разными способами. Следующий пример печатает первую плюс четыре следующие строки относительно первой:

-n '1,+4p'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
John Quincy Adams, 1825-1829
Andrew Jackson, 1829-1837
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825

GIST | Тильда диапазон вида A~B выполнит заданную команду на каждую строку B, начиная со строки A. Следующий интересный пример выполняет команду печати для всех нечётных строк:

-n '1~2p'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
John Quincy Adams, 1825-1829
Andrew Jackson, 1829-1837
George Washington, 1789-1797
Thomas Jefferson, 1801-1809
James Monroe, 1817-1825
Andrew Jackson, 1829-1837

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

УДАЛЕНИЕ

GIST | Для удаления текста в «sed» предусмотрена команда «d» (delete). Удалим нечётные строки используя тильда диапазон с прошлого примера:

'1~2d'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
John Quincy Adams, 1825-1829
Andrew Jackson, 1829-1837
John Adams, 1797-1801
James Madison, 1809-1817
John Quincy Adams, 1825-1829

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

ЗАМЕНА

GIST | Команда «s» (substitute) – это команда замены. В данном примере три слеша разделяют команду, регулярное выражение и то, чем это выражение нужно заменить. В качестве разделителя необязательно использовать слеши, это могут быть любые другие одинаковые символы. Разделителем считается первый символ, который будет встречен после «s»:

's/o/@/'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
Ge@rge Washington, 1789-1797
J@hn Adams, 1797-1801
Th@mas Jefferson, 1801-1809

GIST | По умолчанию команда «s» выполняет замену только первого совпадения в строке, после чего переходит к следующей строке. Если требуется заменить все совпадения, нужно использовать дополнительный флаг «g» (global), который помещается после шаблона замены:

's/o/@/g'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
Ge@rge Washingt@n, 1789-1797
J@hn Adams, 1797-1801
Th@mas Jeffers@n, 1801-1809

GIST | Чтобы заменить только каждое второе совпадение в каждой строке, вместо флага «g» нужно указать числовой модификатор «2». Числовой модификатор указывает, какое по счету совпадение подлежит замене:

's/o/@/2'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washingt@n, 1789-1797
John Adams, 1797-1801
Thomas Jeffers@n, 1801-1809

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

-n 's/o/@/2p'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washingt@n, 1789-1797
Thomas Jeffers@n, 1801-1809

GIST | Флаги тоже можно комбинировать. Например глобальный флаг «g» и флаг поиска без учёта регистра «i» (ignore case):

's/G/@/ig'
George Washington, 1789-1797
@eor@e Washin@ton, 1789-1797

Прежде чем двигаться дальше, для закрепления пройденного материала ответьте для себя на вопрос, что делает следующее выражение '1s/o/@/ig' ? Ответ GIST.

ОБРАТНЫЕ ССЫЛКИ

GIST | Для начала давайте освежим в памяти что делает звёздочка в регулярных выражениях. Метасимвол звездочка означает повторить предыдущий символ или выражение ноль или больше раз. Это позволяет захватывать текст, не зная точной фразы:

's/^.*on/REPLACED/'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
REPLACED, 1789-1797
John Adams, 1797-1801
REPLACED, 1801-1809

GIST | Ссылка «&» указывает на всё подвыражение, удовлетворяющее заданному регулярному выражению, целиком. Например, чтобы взять в фигурные скобки весь текст, совпадающий с шаблоном:

's/^.*on/{&}/'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
{George Washington}, 1789-1797
John Adams, 1797-1801
{Thomas Jefferson}, 1801-1809

GIST | Группы в регулярных выражениях обозначаются метасимволами в виде круглых скобок и имеют смысл, схожий с математическими выражениями. В «sed» можно ссылаться по отдельности на каждую группу, используя её порядковый номер отсчитывая слева:

's/\([a-zA-Z0-9]\+\) \([a-zA-Z0-9]\+\)/\2 \1/'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
Washington George, 1789-1797
Adams John, 1797-1801
Jefferson Thomas, 1801-1809

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

ЦЕПОЧКИ СКРИПТОВ

GIST | До этого мы всегда передавали в «sed» только один скрипт, но иногда возникает необходимость выполнить сразу несколько. Пример выполнения множества скриптов:

-e 's/James/J./' -e 's/George/G./'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
G. Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
J. Madison, 1809-1817
J. Monroe, 1817-1825

GIST | А можно вообще объединить скрипты в один при помощи символа точки с запятой. Этот метод работает точно так же как и предыдущий:

's/James/J./;s/George/G./'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
G. Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
J. Madison, 1809-1817
J. Monroe, 1817-1825

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

ПРОДВИНУТАЯ АДРЕСАЦИЯ

GIST | Вернёмся к адресации. В качестве критериев выбора строк (адресации), которые редактирует «sed», также разрешается использовать регулярные выражения:

'/John Adams/s/.*/Hello/'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, 1789-1797
Hello
Thomas Jefferson, 1801-1809

GIST | Регулярные выражения могут быть использованы в любой части диапазона. Следующий пример удалит все строки от первого найденного слова START до первого найденного слова END:

'/^START$/,/^END$/d'
George Washington, 1789-1797
John Adams, 1797-1801
START
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
END
James Monroe, 1817-1825
George Washington, 1789-1797
John Adams, 1797-1801
James Monroe, 1817-1825

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

'/John Adams/!s/.*/Hello/'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
Hello
John Adams, 1797-1801
Hello

Инверсия точно так же работает с обычной нумерацией.

ДОПОЛНИТЕЛЬНЫЙ БУФЕР

Дополнительный буфер (hold buffer) в «sed» позволяет выполнять многострочное редактирование. Наличие этого буфера позволяет хранить строки во время работы над другими строками. Команды для работы с буфером:

  • h: копирует текущий буфер обработки (последней совпавшей строки, с которой вы работаете) в дополнительный буфер

  • H: добавляет текущий буфер обработки в конец текущей дополнительной обработки, разделяя их символом новой строки

  • g: копирует текущий дополнительный буфер в текущий буфер обработки. Предыдущий буфер обработки будет утерян

  • G: добавляет текущий шаблон в текущий буфер обработки, разделяя их символом новой строки

  • x: Подкачивает текущий шаблон и дополнительный буфер

GIST | На закуску такой вот нехилый примерчик для склеивания смежных строк:

-n '1~2h;2~2{H;g;s/\n/ /;p}'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
George Washington, 1789-1797 John Adams, 1797-1801
Thomas Jefferson, 1801-1809 James Madison, 1809-1817

1~2h – диапазон адреса, каждая нечётная строка, начина с первой, копируется в дополнительный буфер командой «h». Затем берётся каждая чётная строка 2~2. Остальная часть команды взята в фигурные скобки. Это означает, что эта часть команды будут наследовать адрес, который был только что указан. Без этих скобок, наследовать адрес будет только команда «H», а остальные команды будут выполняться для каждой строки. В фигурных скобочках буферы склеиваются в одну строку, после чего символ новой строки заменяется символом пробела и результат печатается на экран. Ура !

links

social