digiwhite 23.01.2011 21:35

ПереводыSed в примерах. Часть 2

Оригинал
Как дальше пользоваться текстовым редактором из мира UNIX?



Замена!

Давайте взглянем на одну из наиболее полезных команд sed - команду замены. Используя ее, мы можем заменять конкретную строку или совпадение с регулярным выражением на другую строку. Вот пример основного применения этой команды:
1
$ sed -e 's/foo/bar/' myfile.txt


Выполнение этой команды приведет к выводу содержимого файла myfile.txt в поток стандартного вывода (sdtout), где первое вхождение подстроки 'foo' в каждой обрабатываемой строке (если конечно такая подстрока будет найдена) будет заменено на подстроку 'bar'. Обратите внимание, что я сказал "первое вхождение" подстроки в каждой строке и обычно это не то, что вы хотите получить. Обычно, когда я выполняю замену в строках, я хочу чтобы это делалось глобально. Поэтому, если я хочу делать это для всех вхождений в каждой строке, то мне подойдет следующая команда:

$ sed -e 's/foo/bar/g' myfile.txt

Дополнительный параметр 'g' после последнего слэша указывает sed на то, что замену необходимо производить глобально.

Есть еще несколько вещей, которые вы должны знать об использовании команды замены 's///'. Во-первых, это команда и ничего более; здесь нет указания адреса как в любом другом примере из первой части. Это означает, что команда 's///' может быть использована вместе с указанием адреса для конкретизации строк, для которых необходимо выполнить команду. Например:

$ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt

В результате выполнения команды из этого примера произойдет замена слова "enchantment" на слово "entrapment", но только для строк с первой по десятую включительно.

$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt

В результате выполнения команды из этого примера произойдет замена слова "hills" на слово "mountains", но только в том блоке текста, который начинается с пустой строки и заканчивается строкой, начинающейся с трех символов 'END' включительно.

Другая примечательная вещь, связанная с командой s/// это то, что для нее имеется множество опций связанных с разделителем '/'. Если мы выполняем замену строк и регулярное выражение или подстрока, на которую необходимо заменить искомую строку содержит множество слэшей внутри себя, то мы можем заменить разделитель указав замену в виде другого символа сразу после 's'. Следующий пример заменит все вхождения /usr/local на /usr:

$ sed -e 's:/usr/local:/usr:g' mylist.txt

Примечание:

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


Регулярные выражения: проблемы

До сих пор в наших примерах мы выполняли простую замену строк. Хоть это и удобно, но мы так же можем выполнять замену с помощью регулярных выражений. Например, следующая команда sed будет искать фразу, начинающуюся с символа '<' и заканчивающуюся символом '>'. Фраза так же будет включать себя любое количество символов между '<' и '>'. Эта фраза будет удалена (заменена на пустую строку):

$ sed -e 's/<.*>//g' myfile.html

Это хорошая попытка использовать sed для удаления HTML тэгов из файла, однако она будет работать плохо из-за особенностей регулярного выражения. Почему? Когда sed выполняет сравнение строки с регулярным выражением, то он ищет самую длинную подстроку, совпадающую с ним. В моей предыдущей статье о sed этой проблемы не возникало, потому что мы использовали команды d и p, которые удаляли или печатали всю строку целиком. Но, если мы используем команду s///, в этом случае определенно имеется большое отличие, т.к. вся часть строки, которая совпала с регулярным выражением будет заменена на другую строку или, как в данном случае, удалена. Это означает, что вышеописанный пример, примененный к строке:

<b>This</b> is what <b>I</b> meant.

Выдаст результат:

meant.

Это отличается от того, что мы хотели, не правда ли:

This is what I meant.

К счастью, это легко исправить. Вместо написания регулярного выражения "после символа '<' следует любое количество любых символов и завершается символом '>'" необходимо написать регулярное выражение "после символа '<' следует любое количество символов кроме символа '>' и завершается символом '>'". В результате мы получим самое короткое из возможных совпадений с регулярным выражением, а не одно единственное, но самое длинное. Новая команда выглядит так:

$ sed -e 's/<<^>>*>//g' myfile.html

В этом примере последовательность '<^>>' означает символ "не '>'", а '*' после означает "ноль или более символов не '<'". Испытайте эту команду на каком-нибудь html файле, передайте результат через канал (конвеер) утилите more и посмотрите что получилось.


Больше символов для сравнения

Последовательность '
' в синтаксисе описания регулярных выражений имеет еще несколько дополнительных значений. Для указания диапазона символов вы можете использовать символ '-', если он не находится в начале или конце выражения (На самом деле это справедливо только для английского языка и арабских цифр. Что касается русского языка, то указать диапазон вида '<а-я>' не получится. Это не будет работать. Полезную информацию по данному вопросу можно почерпнуть по ссылке: http://www.icu-project.org/docs/papers/iuc26_regexp.pdf. Примечание переводчика.). Например:

'*'

Такое регулярное выражение соответствует нулю или более символов из диапазона: 'a', 'b', 'c', ..., 'v', 'w', 'x'. Дополнительно еще имеется класс символов '<:space:>' для поиска пробелов и табуляций.

Класс символовОписание
<:alnum:>Алфавитно-цифровые символы
<:alpha:>Алфавитные символы
<:blank:>Символ пробела или табуляции
<:cntrl:>Любой из управляющих символов
<:digit:>Цифры <0-9>
<:graph:>Любой печатаемый символ (т.е. без пробелов, табуляций)
<:lower:>Алфавитные символы в нижнем регистре
<:print:>Печатаемые символы (не управляющие символы)
<:punct:>Символы пунктуации
<:space:>Символы пробела, табуляции и перевода страницы
<:upper:>Алфавитные символы в верхнем регистре
<:xdigit:>Цифровые символы в шестнадцатиричной системе счисления <0-9 a-f A-F>


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


Расширенные возможности замены

Мы уже научились выполнять простую, но достаточно комплексную замену, однако sed может гораздо больше. Sed предоставляет возможность ссылаться либо на части либо на всю подстроку, совпавшую с регулярным выражением и, соответственно, использовать эти части для конструирования строки на замену. Скажем, вы отвечаете на сообщение. Следующий пример добавляет префикс "ralph said" к каждой строке:

$ sed -e 's/.*/ralph said: &/' origmsg.txt

Результат будет выглядеть так:

ralph said: Hiya Jim,
ralph said:
ralph said: I sure like this sed stuff!
ralph said:

В этом примере мы используем символ '&' в строке на замену. Этот символ указывает sed, что необходимо вставить всю подстроку, совпавшую с регулярным выражением. Итак, все, что совпало с '.*' (самая длинная группа из нуля или более символов в строке, другими словами - вся строка) может быть вставлено везде в строке замены, причем даже несколько раз. Это великолепно, но sed еще мощнее.


Прекрасные экранированные круглые скобки

Команда s/// позволяет нам определять группы внутри регулярных выражений, и это даже лучше, чем использование '&'. Благодаря этому мы можем ссылаться на конкретную группу в строке замены. Допустим, что у нас есть файл, содержащий следующий текст:

foo bar oni
eeny meeny miny
larry curly moe
jimmy the weasel

Теперь, скажем, мы хотим написать скрипт с использованием sed, который заменяет "eeny meeny miny" на "Victor eeny-meeny Von miny" и т.д. Чтобы сделать это, мы сначала напишем регулярное выражение, соответствующее трем строкам, разделенных пробелами:

'.* .* .*'

Есть. Теперь мы определим группы, вставив круглые скобки, экранировав их обратными слэшами, вокруг каждой из них:

'\(.*\) \(.*\) \(.*\)'

Это регулярное выражение работает также, как и первое, но в данном случае определены три логические группы, на которые мы можем ссылаться в строке замены. Вот конечный вариант команды sed:

$ sed -e 's/\(.*\) \(.*\) \(.*\)/Victor \1-\2 Von \3/' myfile.txt

Как видите, в строке замены мы ссылаемся на каждую группу, заданную в круглых скобках, с помощью выражения '\x', где 'x' номер группы, начинающийся с единицы. Результат:

Victor foo-bar Von oni
Victor eeny-meeny Von miny
Victor larry-curly Von moe
Victor jimmy-the Von weasel

Когда вы познакомитесь с sed поближе, то сможете легко и мощно выполнять обработку текста с минимумом усилий. Когда вы задуматесь над решением задачи с использованием любимого скриптового языка, то еще подумайте вот о чем: может вы можете легко ее решить командой в одну строку?


Смешение вещей

Раз уж мы начинаем создавать более сложные sed скрипты, то необходимо наличие возможности для ввода более одной команды. Для этого существует несколько способов. Первый способ - использовать точку с запятой между командами. Например, последовательность команд, использующая команду '=', которая указывает sed печатать номер строки, также как команда 'p', которая указывает sed печатать всю строку (когда мы в режиме '-n'):

$ sed -n -e '=;p' myfile.txt

Во всех случаях, когда указываются две или более команды для sed, то каждая команда применяется к каждой строке файла (в том порядке, в котором они заданы). В вышеописанном примере к строке 1 сначала применяется команда '=', затем применяется команда 'p'. Далее sed переходит к обработке второй строки и процесс повторяется. Использование точки с запятой в качестве разделителя хоть и удобно, но есть ситуации, когда применять ее не представляется возможным. Поэтому есть альтернативный способ для отделения нескольких команд друг от друга, а именно использовать опцию -e для каждой команды:

$ sed -n -e '=' -e 'p' myfile.txt

Однако, когда мы перейдем к более сложным скриптам sed с командами вставки и добавления, то даже множественное использование опции -e не поможет. Для сложных многострочных скриптов лучшим выходом будет сохранение команд в отдельные файлы, на которые можно ссылаться с помощью опции -f:

$ sed -n -f mycommands.sed myfile.txt

Возможно этот способ менее удобен, однако он всегда работает.


Несколько команд на один адрес

Иногда вам может понадобиться указать несколько команд, применяемых только по одному адресу. Это удобно, если вы выполняете большое количество команд вида 's///' для изменения слов или синтаксиса в исходных файлах. Чтобы выполнить несколько команд для одного адреса, запишите ваши команды sed в файл, используя символы '{' и '}' для группировки, например так, как это показано ниже:

1,20{
s/inux/GNU\/Linux/g
s/samba/Samba/g
s/posix/POSIX/g
}

Эти три команды замены будут применены для строк с первой по двадцатую включительно. Так же возможно использовать регулярные выражение для указания адреса или же использовать комбинацию из регулярных выражений и явного задания адресов:

1,/^END/{
s/inux/GNU\/Linux/g
s/samba/Samba/g
s/posix/POSIX/g
p
}

В этом примере команды между '{' и '}' будут применены для строк файла, начиная с первой и заканчивая строкой, содержащей в начале буквы "END" или же, если "END" в исходном файле не обнаружится, то для всех строк.


Добавить, вставить и изменить строку

Теперь, когда мы пишем скрипты sed в отдельные файлы, есть возможность воспользоваться командами добавления, вставки и изменения строки. Эти команды вставляют строку после текущей, перед текущей или заменяют текущую строку в соответствии с шаблоном. Эти же команды могут быть использованы для вставки нескольких строк для вывода. Команда вставки строки задается следующим образом:

i\
This line will be inserted before each line

Если не указывать адрес для команды вставки, она будет применена для каждой строки и, в результате, будет получен вывод, похожий на это:

This line will be inserted before each line
line 1 here
This line will be inserted before each line
line 2 here
This line will be inserted before each line
line 3 here
This line will be inserted before each line
line 4 here

Если вы хотите вставить несколько строк перед текущей, то необходимо добавить обратный слэш после строки замены, предшествующей текущей строке замены. Это может выглядеть следующим образом:

i\
insert this line\
and this one\
and this one\
and, uh, this one too.

Команда добавления работает аналогично команде вставки, но вставляет строку или строки после текущей строки, соответствующей заданному шаблону. Задается эта команда следующим образом:

a\
insert this line after each line. Thanks! :)

С другой стороны, команда "изменить строку" фактически заменяет содержимое текущей строки в рамках совпадения с шаблоном. Используется это так, как показано ниже:

/10/ c\
replace on this line

Все строки в файле, которые содержат число 10, будут целиком заменены на строку "replace on this line". (Вышеописанный пример придуман переводчиком, т.к. пример для данной команды в оригинальной статье таинственным образом отсутствует, хотя по тексту явно видно, что он должен быть).

Т.к. использование команд добавления, вставки и изменения строк требуют для своего использования нескольких строк, то я думаю, что вы захотите сохранять их как текстовый скрипт sed и затем указывать ему на них с помощью опции '-f '. Использование других способов для передачи этих команд sed будет черевато большими трудностями.


Анонс следующей части

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


Дополнительные ресурсы
Наш перевод статьи "Sed в примерах. Часть 1" и "Sed в примерах. Часть 3".
Другие статьи Реда Дэниэлса на Fantoo: "Sed в примерах. Часть 1" и "Sed в примерах. Часть 3".
Замечательный FAQ по sed от Эрика Пимента.
Исходные коды sed на ftp://ftp.gnu.org/pub/gnu/sed.
Список удобных однострочных sed скриптов от Эрика Пимента, который должны посмотреть те, кто стремиться стать гуру sed.
Если вам нравятся хорошие старомодные книги, то книга O'Reilly's sed & awk, 2nd Edition будутет отличным выбором.
Посмотрите how-to по регулярным выражениям на сайте python.org.
Ссылка на обзор по регулярным выражениями от Университета Кнтуки.
Оригинал (английский)
Переведено при помощи сервиса translated.by инициативной группой переводчиков welinux при участии пользователей digiwhite и Zereal.

http://translated.by

Продолжение следует...


Тэги: editors regexp sed редактор ругулярные выражения
+ 19 -
Похожие Поделиться

Dmitry.Minsky 23.01.2011 22:22 #
Вот жеж, а я уже собирался начать переводить эти маны. И как-то пропустил тут первую часть. Кстати, на сайте фунту, довольном много интересных материалов, многие из которых мы уже перевели совместными усилиями на хабре. http://habrahabr.ru/blogs/linux/110697/ тут последняя выложенная версия с навигацией. Дальше в планах перевод руководств по awk.
digiwhite 23.01.2011 22:31 #
Здорово. Ссылку вашу сохраню.

По факту - меня нет на хабре и особого желания туда попадать нет.

По циклу статей: третья часть частично уже переведена. В ближайшее время, я думаю, мы ее закончим.
mutantcornholio 23.01.2011 22:35 #
Пока нет времени прочитать. Но огромное спасибо, давно хочу освоить sed. Плюсик вам.