История и идеология
sed действительно является редактором потока (Stream EDitor - редактор потока), по умолчанию он принимает строки со стандартного ввода (stdin) и выдает на стандартный вывод (stdout). Поэтому применение подобное примеру вполне правомочно:
cat /etc/passwd | sed '2,4!d' | sed '2d'
Но sed - это не только редактор, но и язык программирования, хотя языком его сложно назвать.
Одной из особенностей sed следует считать то, что он первоначально загружает в память правила обработки и, только после этого, загружая входные данные построчно, начинает обработку. То есть sed теоретически способен обрабатывать файл любого размера.
sed был создан в BellLabs как утилита Unix. Первым автором был Lee E. McMahon. Это одна из наиболее ранних команд Unix. В настоящий момент существуют реализации практически для всех операционных систем. По части использования регулярных выражений sed беспорно является первым в истории. sed и awk считают предшественниками perl'а.
Запуск программы
Формат запуска программы очень прост:
sed [ПАРАМЕТРЫ]... {сценарий} [входной-файл]...
Описание ключей запуска
- -n, --quiet, --silent
- подавлять автоматический вывод пространства шаблонов.
- -e сценарий, --expression=сценарий
- добавить сценарий к командам для исполнения
- -f файл-сценария, --file=файл-сценария
- добавить содержимое файла сценария к командам для исполнения.
- -i[суффикс], --in-place[=суффикс]
- редактировать файлы "на месте" (создает резервную копию, если задан суффикс)
- -l N, --line-length=N
- установить желаемую длину строки при переносе строк (line-wrap) для команды `l'
- -r, --regexp-extended
- использовать расширенные регулярные выражения
- -s, --separate
- рассматривать входные файлы раздельно, а не как один продолжающийся поток
- -u, --unbuffered
- загружать как можно меньше данных на входе и сбрасывать как можно чаще буфера на выходе
- --help
- отобразить эту подсказку и выйти
- -V, --version
- вывести информацию о версии и выйти
При использовании большого сценария возможен следующий способ: создается файл, в котором записываются команды sed, файл делается исполняемым, в начало файла добавляется строка:
#!/bin/sed -f
Команды редактирования
Общее описание
Собственно сценарий состоит из команд редактирования, по одной команде в строке и имеет следующий формат
[ адрес 1 [, адрес 2 ]] команда [ аргументы ]
- адрес 1 и адрес 2
- Обычно номера строк. Для обозначения последней строки используется символ $. Если адреса не указаны, то обрабатываются все строки. Если адрес только один, то выбирается отдельная строка. При двух адресах выбирается диапазон строк в заданом интервале.
- Для определения адреса строки допускается применение регулярных выражений. В этом случае адрес заключается между двух символов /
- команда
- Собственно сама команда. Стоит отметить использование конструкции такого вида "!команда". В этом случае команда используется для строк не выбранных по адресам.
- аргументы
- Ну тут все понятно: нечто, с чем команда должна что-то сделать.
В многострочных командах для экранирования продолжения строки используют "\".
Метасимволы
В некоторых командах и при задании адресов используются регулярные выражения использующие метасимволы.
- Регудярное выражение для определения адреса указывается в разделителях: "/ /". В командах использующих метасимволы обычно так: "команда/метасимволы/метасимволы/параметры"
- Любой символ (кроме специальных: \ [ . ^ * $ ) является самим собой.
- Символ "^" указывает на начало строки.
- Символом доллара ($) обозначают конец строки (не путать с адресом последней строки)
- Точка означает любой символ.
- Звездочка "*" стоящая за выражением означает наличие этого выражение 0 или более раз.
- То есть конструкция ".*" означает любое количество любых символов.
- Квадратные скобки "[ ]" указывают на один из символов, приведенных внутри.
- Конструкция "^[A-Z][a-z]$" означает, что строка состоит из двух символов латинского алфавита: заглавной и прописной.
- Символ крышки в квадратных скобках "[^ ]" указывает на один из символов, кроме приведенных внутри.
- Для экранирования специальных символов применяют "\" (кроме цифр и "(" , ")").
Два пространства
В sed нет переменных, однако есть две области памяти, с которыми можно работать фактически как с переменными. Эти области называют pattern space (пространство образца) и hold space (скорее пространство трюма). В этой статье будем их называть просто: pattern и hold.
Pattern space
pattern содержит последнюю считанную из файла строку (справедливости ради следует сказать, что обе области могут содержать не только одну строку, а множество строк, разделенных символом newline (\n) ). Именно с ней работают основные команды. В pattern читается очередная строка. Здесь помещается результат выполнения некоторых команд
Hold space
hold - это действительно трюм, здесь могут содержаться строки на протяжении всей работы скрипта. Между hold и pattern можно производить обмен данными (добавление, замещение, обмен)
Основные комманды
Вводные замечания
Для показа примеров взят файл filename.ext следующего содержания:
Не нравиться -
Сделай сам.
Нравиться -
Сделай лучше!
В скобках перед командой указано максимальное количество адресов, которые можно использовать в команде.
Команды
(1) a \text
- Добавить "text" после указанной строки (вывести), потом считать следующую.
- без адреса выводит указанный text после каждой строки.
$ cat filename.ext | sed 'a \Just for fan'
Не нравиться -
Just for fan
Сделай сам.
Just for fan
Нравиться -
Just for fan
Сделай лучше!
Just for fan
- При указании адреса выводит "text" после указаной строки
$ cat filename.ext | sed '3a \Just for fan'
Не нравиться -
Сделай сам.
Нравиться -
Just for fan
Сделай лучше!
- В различных руководствах по sedу пишут, что у команды можно использовать не более одного адреса, однако пример приведенный ниже осуществим:
$ cat filename.ext | sed '2,4a \Just for fan'
Не нравиться -
Сделай сам.
Just for fan
Нравиться -
Just for fan
Сделай лучше!
Just for fan
В качестве адреса можно использовать регулярное выражение:
$ cat filename.ext | sed '/Сделай/a \Just for fan'
Не нравиться -
Сделай сам.
Just for fan
Нравиться -
Сделай лучше!
Just for fan
И даже два регулярных выражения:
$ cat filename.ext | sed '/Не/,/Нравиться/a \Just for fan'
Не нравиться -
Just for fan
Сделай сам.
Just for fan
Нравиться -
Just for fan
Сделай лучше!
(?) b label
- Перейти на метку, устанавливаемую, с помощью функции ":" , если label пуст, то перейти в конец скрипта. Очень удобно использовать в sedовских скриптах для ветвления программы по некоторому условию.
(2) c \text
- Удалить pattern и вывести "text".
- Удаляет строки и заменяет их текстом указанным в качестве аргумента команды.
cat filename.ext | sed '2c \Just for fan'
Не нравиться -
Just for fan
Нравиться -
Сделай лучше!
- При указании диапазона удаляет строки диапазона, однако выводит только один экземпляр текста, а не заменяет каждую строку диапазона.
cat filename.ext | sed '2,3c \Just for fan'
Не нравиться -
Just for fan
Сделай лучше!
- Все команды следующие после c в наборе команд не выполняются.
- Следует обратить внимание на следующую особенность: если до команды c была выполнена команда a, то текст, вставляемый командой c вставляется перед текстом, вставленным командой a.
cat filename.ext | sed '2,3a \Just for fan
3c \New line'
Не нравиться -
Сделай сам.
Just for fan
New line
Just for fan
Сделай лучше!
И второй вариант:
cat filename.ext | sed '3a \Just for fan
2,3c \New line'
Не нравиться -
New line
Just for fan
Сделай лучше!
- В первом варианте Just for fan вставляется после второй и третьей строки, но New line вставляется вместо третьей, то есть перед добавлением для третьей.
- Стоит обратить внимание на влияние команды на значение номера строки. Обе команды не изменяют значения номера строки, хотя и добавляют строки в выходной текст.
cat filename.ext | sed '=;3a \Just for fan
2,3c \New line'
1
Не нравиться -
2
3
New line
Just for fan
4
Сделай лучше!
(2) d
- Удалить pattern. Просто строка (или диапазон строк) не выводится, и все
- Все команды после этой команды не исполняются. После выполнения команды считывается следующая строка файла и выполнение команд начинается сначала. Влияет на счетчик строк.
cat filename.ext | sed '=;3a \Just for fan
3d;2,3c \New line'
1
Не нравиться -
2
3
Just for fan
4
New line
(?) D
- Удалить pattern space до вставленной newline.
- Более сложный вариант удаления.
(?) g
- Заместить содержимое pattern space содержимым буфера hold space .
(?) G
- Добавить к содержимому pattern space содержимое буфера hold space .
(?) h
- Заместить содержимое буфера hold space на содержимое pattern space .
(?) H
- Добавить к содержимому буфера hold space содержимое pattern space .
(2) i \text
- Вывести текст на output перед указанной строкой.
- Практически то-же самое, что и a, только перед строкой.
cat filename.ext | sed '2,3i \Just for fan'
Не нравиться -
Just for fan
Сделай сам.
Just for fan
Нравиться -
Сделай лучше!
- При использовании команды i совместно с командой c следует учитывать ту же особенность, которая указана в описании команды a.
cat filename.ext | sed '2,3i \Just for fan
3c \New line'
Не нравиться -
Just for fan
Сделай сам.
Just for fan
New line
Сделай лучше!
(2) n
- Выводит pattern на output и считывает следующую строку.
- Выполнение команды n прекращает выполнение команд скрипта для текущей строки.
- При использовании в команде одного адреса вопросов не возникло, а вот с двумя адресами - кошмар.
cat filename.ext | sed '1,3n;a \Just for fan'
Не нравиться -
Сделай сам.
Just for fan
Нравиться -
Сделай лучше!
Just for fan
На моем любимом примере плохо видно, что происходит, по этому покажу другие варианты (пустые строчки добавлены для наглядности вывода):
1 пример
seq 10 | sed 'a /JFF'
1
/JFF
2
/JFF
3
/JFF
4
/JFF
5
/JFF
6
/JFF
7
/JFF
8
/JFF
9
/JFF
10
/JFF
|
seq 10 | sed '3,7n;a /JFF'
1
/JFF
2
/JFF
3
4
/JFF
5
6
/JFF
7
8
/JFF
9
/JFF
10
/JFF
|
- Если что нибудь поняли сразу (без учета добавленных мной пустых строк), то я в унынии, ибо мне моего интеллектуального уровня не хватило, что бы понять все в первом чтении (как и депутатам пришлось читать второй, а потом и ... раз)
2 пример
seq 10 | sed 'a /JFF'
1
/JFF
2
/JFF
3
/JFF
4
/JFF
5
/JFF
6
/JFF
7
/JFF
8
/JFF
9
/JFF
10
/JFF
|
seq 10 | sed -n 'p;3,8n;a /JFF'
1
/JFF
2
/JFF
3
/JFF
5
/JFF
7
/JFF
9
/JFF
|
- Вот теперь вроде бы стало ясно, что команда прекращает выполнение скрипта для текущей строки, но прочитав следующую, продолжает выполнение скрипта для нее и, закончив скрипт, читает очередную строку.
- Получается, что читаем третью строку (для первых двух надеюсь все понятно), выводим ее, обнаруживаем, что необходимо прекратить работу для нее, читаем четвертую, добавляем после нее текст и начинаем скрипт с начала. Читаем пятую, выводим, обнаруживаем прекращение обработки скрипта для пятой (3 < 5 < 8), читаем шестую, выводим текст, начинаем скрипт с начала. Но куда делась десятая строка???
3 пример
seq 11 20 | sed -n '=;p;3,8n;=;a /JFF'
- Что бы разобраться, пришлось сочинить такую вот фигуру. Вернее команду. Результат работы можно увидеть ниже.
(приношу свои извинения, если на экране у Вас что-то не нормально отображается. Все делаю из расчета на Mozilla FireFox, в этой рыжей бестии вроде бы все нормально.)
1
11
1
/JFF
2
12
2
/JFF
3
13
4
/JFF
5
15
6
/JFF
7
17
8
/JFF
9
19
10
/JFF
Попробуем рассмотреть все по порядку:
- И так, читаем первую строку, выводим ее номер, выводим ее содержимое. Она не попадает в диапазон. Выводим ее номер, добавляем текст, переходим к обработке следующей строки.
- Читаем вторую строку, выводим ее номер, выводим ее содержимое, игнорируем команду n, выводим номер строки, добавляем текст.
- Читаем третью строку, выводим ее номер, выводи ее содержимое, обнаруживаем, что она попадает в диапазон и прерываем исполнения скрипта, одновременно читая следующую строку. Вот теперь самое интересное - скрипт продолжает работать, но уже для четвертой строки. Выводим номер строки, добавляем текст.
- Читаем следующую строку. Так как четвертая строка у нас оказалась обработанной внутри скрипта, то очередной строкой стала пятая. Для нее песня повторяется. То есть попутно еще обрабатываеми шестую.
- Для очередной (седьмой строки) попутно обрабатываем восьмую.
- Для девятой строки происходит что-то странное. Такое ощущение, что sed просто привык обрабатывать строки парами и куда то дел десятую.
Самое интересное, что такая вот команда дает в принципе правильный (по крайней мере ожидаемый результат).
seq 11 20 | sed -n '=;p;3,7n;=;a /JFF'
- В чем дело?
- А Бог его знает. Если кто нибудь разберется, то, будте добры, поделитесь. Истина, конечно-же, в исходниках. Возможно когда нибудь разберусь сам, тогда и допишу эту часть статьи.
(?) N
- Добавить следующую строку к pattern space , разделяя строки вставленным newline.
(?) p
- Скопировать pattern space на output.
(?) P
- Скопировать pattern space до первой вставленной newline на output .
(?) q
- Переход на конец input . Вывести указанную строку, (если нет флага -n ) и завершить работу sed.
(?) r rfile
- Читать содержимое rfile и вывести его на output прежде чтения следующей строки.
(?) s
- Функция контекстной замены.
(?) t label
- Перейти на метку, устанавливаемую с помощью функции ":" , если для этой строки была осуществлена замена с помощью функции s. Флаг осуществления замены восстанавливается при чтении следующей строки или при выполнении функции s.
(?) w wfile
- Добавить pattern space к концу файла wfile . (Максимально можно использовать до 10 открытых файлов.)
(?) x
- Поменять местами содержимое pattern space и буфера hold space .
(?) y /str1/str2/
- Заменить все вхождения символов из str1 на соответствующие из str2 . Длины строк должны быть равными.
(?) ! func
- Применять функцию func (или группу функций в {} ) к стокам НЕ попадающим в указанные адреса.
(?) : label
- Устанавливает метку для перехода по "b" и "t" командам.
(2) =
- Выводит номер строки как строку.
- В выше приведенных примерах очень часто использовалась. По этому примеров не будет ;)
(?) {
- Выполняет функции до "}" , только когда выбрано pattern space. Группировка функций.
(?) #
- Комментарий.
- ("#n" в скрипте равносильно установке флага -n)
- Пустая команда игнорируется.
Несколько интересных решений
- Вывести количество строк
-
sed -n '$=' filename.ext
- Тут все просто - для последней строки выводи ее номер
- Удалить пустые строки
-
sed '/^ *$/d' filename.ext
- Удаляем строки в которых между началом и концом строки ни одного или несколько пробелов
- Выдать строки файла в обратном порядке
-
sed -n '1h;1n;x;H;$x;$p' namefile.ext
- Как видите - все очень просто. Так как hold в начале работы пуст, то при обработке первой строки перемещаем pattern в hold. Сбрасываем в output содержимое pattern (после предыдущей команды там пусто), если этого не сделать, то первая строка продублируется. После этого для всех строк меняем местами pattern и hold и добавляем новый pattern к новому hold. После того, как обработаем последнюю строку, то переносим в pattern содержимое hold и выводим полученный результат на output. Работа данного сценария проверялась только на небольших файлах. IMHO может быть ограничение на размер файла, связанный с размером памяти отводимой программой для hold.
|
|
|
См. также
Ссылки
Примечание
|
|