Главная Форум Wiki Почта Jabber Devel NIX-FILES
Материал из AltLUG Wiki
Перейти к: навигация, поиск

Потоковый редактор sed

История и идеология

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.

Содержание

См. также

Ссылки

Примечание

Личные инструменты