Использование sed.

Как работает sed?

Утилита sed создана в полном соответствии с принципами UNIX-Way, и пользоваться ей можно так-же как и другими командами shell. sed ближе всего к cat, фактически это тоже самое, отличие лишь одно: cat просто копирует текст, а sed этот текст может и обрабатывать. Для обработки утилитой sed необходимо задать особые sed-команды, впрочем мы вправе никаких команд и не задавать, и тогда получим полный аналог cat (за исключением некоторых незначительных деталей). sed-команды задаются первым параметром, наберём к примеру sed ''. В кавычках я набрал необходимый для утилиты sed-скрипт, в данном случае пустой, потому sed никак не изменит текст. Набрав эту команду мы не увидим ничего, кроме курсора - по умолчанию sed читает входной поток с клавиатуры, и выводит его на экран (как и cat), мы можем так-же перенаправить выходной поток в файл, к примеру sed '' > dest.txt, а можем скопировать один файл в другой: sed '' src.txt > dest.txt, ну и как в случае с cat, мы можем объединять файлы: sed '' *.html > all.html.

Замечание

Так получилось потому, что ни cat, ни sed на самом деле не объединяет файлы. Сначала действует оболочка, раскрывая *.html в отсортированный по алфавиту набор файлов, к пример, если у нас есть 3 файла 1.html, 2.html, и 3.html, то sed получит 4 параметра:
  1. '' - пустой скрипт.
  2. 1.html
  3. 2.html
  4. 3.html
Далее sed последовательно читает все три файла, применяя к каждой строке в них sed-скрипт (пустой в данном случае). Результат выводится в выходной поток. Вот тут действие sed заканчивается, и опять начинает работать оболочка, последовательно записывая весь вывод в файл all.html. Потому код cat примитивен (пара строк на C), она просто читает входные файлы и выводит их в выходной поток, без всякой обработки, поисков и слияний. (Правда cat ещё и может нумеровать строки с ключом -n).

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

Предостережение

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

К примеру рассмотрим самый просто sed-скрипт:

Пример 1.2. Простейший скрипт.

(файл test.c приведён выше)

sed 'p' test.c

#include <stdio.h>
#include <stdio.h>


/* comment */
/* comment */


int main()
int main()
{
{
	printf("тестовый файл\n");
	printf("тестовый файл\n");
	return 0;
	return 0;
}
}


						

Как видите, такой скрипт приводит к удвоению каждой строки. sed обычно выводит обработанные строки, но тут я применил команду p, которая то-же выводит строку, потому строки удваиваются. В отличие от ed, sed применяет команду к каждой строке (если не указан адрес).


Применение адресных выражений в sed.

Так-же как и в ed, доступны адресные выражения, например:

Пример 1.3. Применение адреса.

sed '1d' test.c

/* comment */

int main()
{
	printf("тестовый файл\n");
	return 0;
}
					
Этот скрипт удалил первую строку из файла.

Замечание

На самом деле действие команды d несколько иное, и отличается от действия той-же команды в редакторе ed, однако, для нас это пока не важно. Строки тут действительно удаляются, а тонкие моменты мы рассмотрим позже.


Так-же доступны и более сложные адресные выражения:

Пример 1.4. Сложное адресное выражение.

sed '/{/,/}/!d' test.c
{
	printf("тестовый файл\n");
	return 0;
}
					
Адресное выражение в данном случае /{/,/}/, т.е. от открывающей, до закрывающей фигурной скобки, однако, из-за восклицательного знака после адреса, команда выполняется для тех строк, которые не совпадают с адресным выражением. А команда у нас d, которая удаляет строки из выходного потока. Таким образом, данный скрипт удаляет все строки, кроме тех, которые образуют блок между {}.

Замечание

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


sed-скрипты из нескольких команд.

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

Пример 1.5. Скрипт из нескольких команд.

sed '/i/d;/p/d' test.c

/* comment */

{
	return 0;
}

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

Замечание

На самом деле, этот скрипт работает совсем по другому. Ожидаемого результата можно добиться скриптом /[ip]/d, который во первых использует регулярное выражение (о них в следующей главе), а во вторых тут всего одна команда. Проблема данного варианта в том, что команда d не удаляет строки, а прерывает работу скрипта, потому, найдя в строке i, sed просто перейдёт к анализу следующей строки, не проверяя, есть-ли там буква p, причём перейдёт молча, без всякого вывода. В данном конкретном случае это нас более чем устраивает, это даже будет быстрее, если в тексте больше i, чем p (но медленнее, если много p, а i - мало). Чуть позже мы рассмотрим более подробно работу этой команды, сейчас только могу посоветовать либо не использовать d, либо использовать её последней в скрипте.

Адресное выражение действует только на одну команду, как сделать, что-бы оно подействовало на несколько? Для этого команды нужно заключить в фигурные скобки, например:

Пример 1.6. Объединение команд в блок.

sed '3{p;p}' test.c
#include <stdio.h>

/* comment */
/* comment */
/* comment */

int main()
{
	printf("тестовый файл\n");
	return 0;
}

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

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

sed команды можно разделять не только точкой с запятой, но и записывая по одной или по несколько команд в новой строке. В bash'е это не слишком удобно, однако все эти команды можно записать в отдельный файл, который затем передать sed. Что-бы sed знала, что это не скрипт, а файл со скриптом внутри, воспользуйтесь ключом -f. Так-же можно заставить оболочку самостоятельно выполнить этот скрипт: достаточно в первой строке скрипта записать
#!/bin/sed -f
Кроме того, необходимо разрешить право выполнения этого файла.

Замечание

Как всегда, для запуска скрипта который передаётся интерпретатору (в данном случае sed) достаточно лишь права чтения, однако, для самостоятельного запуска необходимо так-же право на исполнение. Установить это право можно bash-командой chmod +x my_script. Кроме того, текущая директория в Linux обычно не входит в список директорий, в которых производится поиск файлов для исполнения (по соображениям безопасности), потому вам придётся так-же указать путь к файлу, либо (когда вы решите, что это хороший скрипт, и он вам постоянно нужен) скопировать его в один из каталогов с исполняемыми файлами (конечно для этого потребуются права root'а, обычные юзеры не должны иметь прав делать файлы доступными для выполнения всеми подряд во всей системе). Что-бы злоумышленник не смог изменить ваш скрипт, после копирования смените владельца и группу на root:root и установите права 0755.

Внимание

Ну и к sed-скрипту относятся всё, что и к обычной программе: там тоже возможны уязвимости, глюки, и баги. Его тоже можно "сломать", к примеру скормив ему особым образом приготовленный файл, который выполнит необходимые злоумышленнику действия. Невозможно запретить чтение исполняемого файла(имеется ввиду - скрипта), потому злоумышленник всегда может посмотреть код скрипта, который он может исполнять. Потому sed-скрипты считаются опасными. Однако, я думаю, что опасность sed-скриптов не выше (ИМХО ниже) чем опасность любого другого файла, который так-же всегда можно прочитать. Как всегда, опасность подстерегает только тех, кто слабо разбирается что он делает. Использование C намного опаснее - ведь ваш компилятор и используемые вами библиотеки новые, и о многих уязвимостях вам не известно (точнее никому сегодня не известно, но будет известно злоумышленнику завтра). Конечно, не стоит устанавливать SUID бит скрипту (я правда не знаю, возможно-ли это, никогда так не делал), для получения необходимых прав используйте sudo.
Использовать перевод строки в качестве разделителя команд может потребоваться в том случае, если команда имеет параметр, который может содержать точку с запятой (например команда a, которая добавляет произвольный текст после строки).

Подсказка

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

sed и vim

Sed-скрипты можно писать в любом редакторе, однако (кто-бы сомневался :-) ) удобнее всего использовать vim - в отличие от других редакторов vim распознаёт sed-скрипты и раскрашивает их:

Рисунок 1.1. Редактирование sed-скрипта в vim.

Редактирование sed-скрипта в vim.


Вы можете скачать мой файл раскраски sed.vim. (тот что шёл по умолчанию - недоделанный, там не хватает некоторых команд и флагов). У меня этот файл лежит в /usr/share/vim/vim72/syntax/sed.vim.

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

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

Подавление вывода ключом -n

После обработки каждой строки sed выводит её в выходной поток, используя ключ -n вы можете запретить это.

Пример 1.7. Использование ключа -n

sed -n '3p' test.c
/* comment */
тут выводится только третья строка.

Замечание

Есть альтернатива - запретить вывод всех строк кроме третьей, sed '3!d' test.c, но лучше так не делать, из-за особенностей команды d.

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

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