Содержание
sed обрабатывает поток последовательно, строчка за строчкой, начиная с первой и кончая последней (конечно если в sed-скрипте не указано иного, например можно обрабатывать только первые строки, и завершить обработку после выполнения какого-нибудь условия), обычно каждая строка обрабатывается отдельно, в три этапа.
Процедура 2.1. Обработка текста утилитой sed.
Загрузка строки из потока.
На этом этапе строка загружается в буфер. Буфером называется выделенная sed область памяти, размер которой не ограничен (для GNU версии sed, ну конечно на практике размер ограничен объёмом оперативной, и swap-памяти).
Загрузка заканчивается после чтения из потока символа новой строки (\n), или после завершения потока. При этом символ новой строки хотя и читается из потока, однако не пишется в буфер.
На этом этапе выполняется sed-скрипт, при этом содержимое буфера обычно изменяется. sed-скрипт состоит из особых sed-команд, каждая из которых представляет собой одну из букв латинского алфавита. Как обычно, малые и БОЛЬШИЕ буквы различаются: n и N это разные команды. Проще всего записывать sed-команды в командной строке, сразу после sed и её ключей
, например:
sed -n 'p;p;p'
Для разделения команд используется точка с запятой (;).
Команды sed могут изменить содержимое буфера, но кроме того, как и в других языках программирования, в sed-скриптах можно применять команды условных и безусловных переходов (b, t, и T), имеются также команды прерывания работы (q и Q). Некоторые команды воздействуют не только на этап обработки строки, но и на другие этапы, кроме того, внутри скрипта можно ввести ещё одну или несколько строк из входного потока (как на первом этапе).
Перед (почти)любой командой sed вы можете поставить адресное выражение, в таком случае, команда выполнится тогда, и только тогда, когда адресное выражение истинно. В качестве адресного выражения можно использовать
Команда выполнится только если в буфере найдётся данное RE.
Можно создать и более сложное условие, к примеру от заданного RE, и до строки $ (до конца). Или от первого RE до второго (включительно).
Кроме написания скрипта сразу после команды, вы можете записать его в файл, для выполнения такого файла можно использовать опцию -f
, например:
$ sed -f my_script.sed test_file.txt
Эта команда выполнит sed-скрипт my_script.sed
для файла test_file.txt
. Кроме того, используя sha-bang
#!/bin/sed -f
вы можете заставить исполнять ваши скрипты оболочку, например, если вы дописали в ваш скрипт в первую строку этот sha-bang, и кроме того у вас есть право выполнения этого скрипта, то прошлый пример можно выполнить так:
$ ./my_script.sed test_file.txt
После завершения работы скрипта sed выводит содержимое буфера в выходной поток. Однако, это далеко не всегда необходимо, если вам это не нужно, воспользуйтесь опцией -n
, которая блокирует вывод буфера. Кроме того, на этом же этапе происходит вывод и некоторой другой информации, если в скрипте выполнились команды a, c, и/или i. Эти команды тоже выводят информацию в выходной поток, но не во время исполнения, а в этом этапе. Существуют три команды (d, D и Q), которые так-же подавляют вывод буфера на этом этапе.
Для начала изучения sed нам потребуется какой-нибудь простой текст, например этот:
Пример 2.1. Текст используемый для проверки скриптов.
В чём отличие Linux от Windows? Идеологические отличия Отличия огромны. Достаточно сказать, что Linux всё-таки надо рассматривать как один из диалектов операционной системы UNIX, в то время как Windows является оригинальной разработкой компании Microsoft и начиналась как надстройка над DOS. Таким образом всё что появилось в Windows спустя годы после выхода в свет: многозадачность, сетевые протоколы и т.д., в Linux присутствовало практически изначально. Многие вещи были просто портированны (перенесены) в Linux из UNIX. Многие вещи, которые есть в Linux недоступны в Windows до сих пор. Например, вы можете оценить многозадачность запустив в Windows и Linux копирование файлов на дискету. В то время как в Windows до сих пор работать с чем-то ещё становится просто невозможно, Linux ведёт себя так, как будто запись вообще не идёт - он не замечает этот процесс. Если Windows изначально строилась как средство улучшенного взаимодействия с пользователем и не может работать без графического интерфейса, Linux вполне может обходится без привычных окон, мышек и всего остально антуража, тем самым экономя память и снижая требования к ресурсам машины при использовании Linux в качестве сервера. Windows имеет редакции Server (Сервер) и Workstation (Рабочая станция), но в Linux всё не так. Устанавливая Linux вы получаете полный набор программного обеспечения и можете использовать эту операционную систему и как сервер и как рабочую станцию. Вам предоставляется полная свобода выбора, без всяких кодов и ключей и только от вас зависит как вы будете использовать ваш Linux: хоть в качестве сервера на предприятии, где вы работает, хоть дома в качестве рабочей машины или вообще на ноутбуке.
Такой вот коротенький текстик. Как обычно, примеры кодов и текстов я буду набирать моноширным шрифтом, что-бы все буквы были одинаковыми. Что-бы создать тестовый файл, сначала скопируйте в буфер этот текст, а затем в каком-нибудь эмуляторе терминала (в командной строке, но только не в той, что вызывается по CTRL+ALT+Fn) Наберите:
$ cat > test.txt
После чего, можно вставлять буфер, как? Зависит от терминала, обычно работает CTRL+INS, может сработает левая или средняя кнопка мыши, а может в меню есть нужный пункт, после вставки нажмите CTRL+D, для указания того, что файл завершён. Кроме того, можно воспользоваться gvim'ом, либо любым другим текстовым редактором.
Теперь можно написать первый sed-скрипт:
$ sed '' test.txt
Этот скрипт просто выводит на экран терминала наш тестовый файл. Это не слишком интересно, точно так-же работает та-же команда cat. Этот примитивный пример говорит о многом: во-первых, две одиночные кавычки - это сам скрипт, между кавычками ничего нет, потому скрипт ничего не делает - не меняет никакой строчке в потоке.
Во-вторых, обратите внимание: сначала идёт скрипт, а после него идут файлы.
Теперь немного изменим наш "скрипт", заставим sed поменять все слова "Windows" на "Must Die", для этого нам понадобится sed-команда s, эта команда ищет в строке нужную одну строку, и заменяет её на другую:
$ sed 's/Windows/Must Die/' test.txt В чём отличие Linux от Must Die? Идеологические отличия Отличия огромны. Достаточно сказать, что Linux всё-таки надо рассматривать как один из диалектов операционной системы UNIX, в то время как Must Die является оригинальной разработкой компании Microsoft и начиналась как надстройка над DOS. Таким образом всё что появилось в Must Die спустя годы после выхода в свет: многозадачность, сетевые протоколы и т.д., в Linux присутствовало практически изначально. Многие вещи были просто портированы (перенесены) в Linux из UNIX. Многие вещи, которые есть в Linux недоступны в Must Die до сих пор. Например, вы можете оценить многозадачность запустив в Must Die и Linux копирование файлов на дискету. В то время как в Must Die до сих пор работать с чем-то ещё становится просто невозможно, Linux ведёт себя так, как будто запись вообще не идёт - он не замечает этот процесс. Если Must Die изначально строилась как средство улучшенного взаимодействия с пользователем и не может работать без графического интерфейса, Linux вполне может обходится без привычных окон, мышек и всего остально антуража, тем самым экономя память и снижая требования к ресурсам машины при использовании Linux в качестве сервера. Must Die имеет редакции Server (Сервер) и Workstation (Рабочая станция), но в Linux всё не так. Устанавливая Linux вы получаете полный набор программного обеспечения и можете использовать эту операционную систему и как сервер и как рабочую станцию. Вам предоставляется полная свобода выбора, без всяких кодов и ключей и только от вас зависит как вы будете использовать ваш Linux: хоть в качестве сервера на предприятии, где вы работает, хоть дома в качестве рабочей машины или вообще на ноутбуке.
Ага, сработало!
Но не всё так радужно, если мы попробуем поменять "Linux" на "TRUE OS" нас ждёт разочарование:
Не все слова "Linux" заменяются, некоторые остаются без изменений. Дело в том, что sed по умолчанию заменяет только первое найденное слово... Исправить это просто: достаточно в конце команды s добавить модификатор g (команда - это не только буква s, но и то что идёт в слешах: слово для поиска и слово для замены).
sed 's/Linux/TRUE OS/g' test.txt
Модификатор g позволяет заменять все совпадения. Имеются цифровые модификаторы, которые позволяют заменить лишь некоторые вхождения:
echo "aaaaaaa" | sed 's/a/A/3' aaAaaaa
Меняется только третья буква «a».
Цифровой модификатор можно комбинировать с модификатором g:
echo "aaaaaaa" | sed 's/a/A/3g' aaAAAAA
Как видите, меняется третья, и все последующие буквы. Т.о. модификатор g заставляет sed продолжать замены до тех пор, пока строка не кончится; а цифровой модификатор говорит о том, с какого совпадения мы начнём замены. Не надо думать, что модификатор g сильно замедлит работу, sed в любом случае просматривает всю строку, даже если она уже сделала единственную замену (безусловно, на сами замены время тратится. Кроме того, уходит время на сдвиг хвоста, после заменяемой части, если длинна заменяемой и заменяющей части не совпадают. При массовых заменах хвост приходится двигать множество раз, что может сильно замедлить выполнение программы).
Кроме модификатора g так-же имеются модификатор i, который приведёт к тому, что sed не будет различать малые и большие буквы (в прошлом примере будет заменено слово lINUX, а так-же LiNuX, и все прочие); Многие команды sed дублируются в модификаторах. К примеру существует команда w для записи в файл. Так же есть и модификатор w с точно таким-же функционалом. Кроме того, имеются и другие модификаторы, которые мы рассмотрим несколько позже.
На самом деле, sed ищет вовсе не слова, а Регулярные выражения. Регулярным выражением называется строка, в которой некоторые символы имеют специальное значение. Если обычный символ просто ищется в строке, то спец-символ задаёт особые правила для поиска.
Я обычно использую не простые, а расширенные регулярные выражения(ERE), ИМХО они более наглядные. Для их использования необходимо использовать sed с ключом -r
. На самом деле это одно и то-же, отличие только в форме записи. Примеры:
Обычное регулярное выражение:
/\(.\+\)\{3\}/
Расширенное выражение:
/(.+){3}/
Как видите, расширенные выражения часто намного проще и короче. Впрочем, так это далеко не всегда, если мы используем спец-символы редко, то удобнее применять обычные выражения, в которых многие спец-символы не требуют экранирования обратным слешем.
Далее примеры совсем простые, потому мне достаточно всего одной строки, потому я буду передавать в sed строку командой echo, вот к примеру, эта команда поменяет первую букву D на z:
$ echo "ABCDEEEEDG" | sed -r 's/D/z/' ABCzEEEEDG
Чаще всего встречается регулярное выражение «.*», оно состоит из двух спец-символов:
В целом это выражение читается так: Любой символ повторяется сколько угодно раз. С таким шаблоном совпадёт любая строка, в том числе и пустая.
$ echo "ABCDEEEEDG" | sed -r 's/.*/z/' z
Вот более сложный пример со спец-символом:
echo "ABCDEEEEDG" | sed -r 's/DE*/z/' ABCzDG
Регулярные выражения следует читать слева-направо, потому как именно так они и просматриваются командой sed. Это выражение означает: Найти букву D, за которой идёт буква E, которая повторяется любое число раз. Тут видно что во-первых, спец-символ «*» действует только на один обычный символ(на букву E в данном случае), и во-вторых, видна жадность звёздочки, она захватила не "любое" количество E, а всё что смогла. В sed все модификаторы ведут себя точно так-же, и нет никакой возможности изменить это поведение, это очень полезно для ускорения работы наших скриптов. На самом деле наша строка делится на четыре части:
Находится только первое совпадение в строке, причём это совпадение берётся с максимально большим числом повторений (если спец-символ повторений допускает любое число совпадений). Конечно, модификатор `g' позволяет найти и заменить все совпадения.
Ещё раз повторю: «*» означает - любое число повторений. В том числе и ноль. Т.е. под регулярное выражение /E*/ попадают и пустые строки тоже, и любые строки, в которых есть пустые строки. К примеру строка "ABC" - в ней регулярное выражение /E*/ можно найти ЧЕТЫРЕ раза:
$ echo "ABC" | sed 's/E*/z/g' zAzBzCz
Как видите, найдено ровно 4 совпадения, при этом, в данной строке вообще нет символа «E».
Всё потому-что, как я уже ранее писал - совпадение, это место между символами. В строке «ABC» имеется 3 символа, и четыре места между символами, именно на эти 4 места и меняется RE /E*/.
Вот следующий пример:
echo "ABCDEEEEDG" | sed -r 's/D./z/g' ABCzEEEz
Тут ищутся все совпадения с шаблоном /D./, что означает: Буква D, после которой идёт один любой символ, здесь найдено 2 совпадения сразу: DE и DG.
обращаю ваше внимание, что "любой символ" должен существовать, например в нашей строке есть `/G/', но нет `/G./', дело в том, что после единственной G нет вообще никаких символов.
Как всегда, речь идёт о символах, а вовсе не о байтах, например "Ф" в кодировке UTF-8 занимает 2 байта, однако sed считает эту букву одним символом. Далее будут неоднократно упоминаться "несимволы", например «\xD1» в кодировке UTF-8, данный несимвол является первой половиной многих русских букв, и не совпадает (сам по себе) ни с одним регулярным выражением. К примеру RE .* совпадает с любой строкой символов, в том-числе и со строкой в которой нет символов, однако, даже это RE не совпадает со строкой, в которой есть хотя-бы один несимвол. Будьте внимательны!
$ echo "ABCDEEEEDG" | sed -r 's/DE?/z/g' ABCzEEEzGнайдено 2 совпадения: `DE' и `D'.
-r
для того, что-бы не экранировать '?'.
$ echo "ABCDEEEEDG" | sed -r 's/D*/z/g' zAzBzCzEzEzEzEzGz $ echo "ABCDEEEEDG" | sed -r 's/D+/z/g' ABCzEEEEzGТакое дело, шаблон `/D*/' меняется на z перед любой буквой "не D", а если буква - D, то она меняется на z. Т.о. например "A" у нас поменялось на "zA". Шаблон `/D+/' меняет на z только подряд идущие буквы D.
Многие неправильно понимают жадность: например в выражении
s/(.*)X.*/\1/
очень часто ошибаются, считая, что захватится из "abc_def_ghi_C" только "abc", основная ошибка: непонимания того, что все звёздочки(и прочие квантификаторы) жадные, и sed обрабатывает строки и выражения слева-направо. Потому данное выражение захватит всё до последней буквы X, а вовсе не до первой - первая звёздочка ТОЖЕ жадная.
Особенно опасны такие случаи:
$ echo "___aaa" | sed -r 's/(.*)(a+)/\1~/' ___aa~
Мы ясно видим «aaa» в конце строки, и ждём, что sed их заменит на «~». Но происходит совсем другое. Дело в том, что sed вовсе ничего не угадывает и не подбирает, всё проще: в данном случае sed ищет два выражения в скобках одновременно. Решение о том, где находится граница принимается в конце разбора. Точнее, после просмотра каждого символа граница сдвигается (новая граница затирает значение старой). По это причине результат работы будет следующий: оба выражения захватят столько символов, что-бы совпадение произошло. Однако, наши квантификаторы могут захватить и больше символов.
Т.к. просмотр идёт слева-направо, то первый (левый) квантификатор захватит максимально возможное число символов. Второй захватит минимально-необходимое. Что мы и наблюдаем в этом примере: первый захватил 5 символов, а второй только один. Потому-что это плюс, была-бы звёздочка, то ей символов не досталось-бы вообще (для звёздочки "пусто" тоже совпадение).
Жадности посвящена большая часть этой книги. Действительно, как хорошо было например в php, что-бы захватить <b>произвольный текст с тегам внутри</b> нам было достаточно использовать нежадную звёздочку. В sed таких нет, есть только жадная, а она захватывает весь текст, до последнего тега.
Кажется, что это серьёзный недостаток sed, но это не так: доказано, что для нежадных квантификаторов невозможен однопроходный алгоритм. В реальных скриптах практически невозможно предсказать, сколько-же проходов будет выполнятся, что приводит к тому, что скрипты отлично работающие при отладке, начинают тормозить и виснуть в реальных применениях. Мало того, злоумышленник может намертво повесить любой сайт, просто сформировав и отправив соответствующее сообщение/комментарий, такой, что-бы php скрипт совершил-бы огромное число проходов для каждого показа этого поста. (конечно, в реальной жизни такой быдлокод обычно всё-же отлавливается при тестировании. И тем не менее.).
Всё это невозможно в sed. Мало того, все ограничения жадности можно обойти, а часто и использовать жадность с пользой.
/[0123456789]/Совпадает с любой цифрой. Допустимо указывать диапазон, тот-же класс можно записать так:
/[0-9]/Это так-же любая цифра. Кроме того, можно задавать инверсию, например любой символ кроме цифр
/[^0-9]/
/[-+]?[0-9]+/теперь запишу это по-русски: символ плюс или минус, которого может и не быть, а затем идёт несколько (хотя-бы один) символ не меньший нуля, и не больший девяти.
/Q{7}/ищет в строке 7 букв Q подряд. Можно задать диапазон, например
/Q{3,9}/ищет от 3х до девяти букв Q, как и при применении остальных квантификаторов, захватывается максимально возможное число символов. Диапазон можно задать частично,
/Q{1,}/эквивалентно
/Q+/
s/.*([-+]?[0-9]+).*/\1/выделит из строки последнее целое десятичное число со знаком.
s/(\S+\s+){3}//сотрёт первые три поля (полем здесь считается последовательность из нескольких не пробельных символов, после которых идут несколько пробельных). Но самое важное применение скобок - поиск одинаковых подстрок, например, это выражение найдёт 2 одинаковых символа:
/(.).*\1/А это выражение находит только одинаковые строки, разделённые символом `\n':
/^(.*)\n\1$/
символы `^' и `$' означаю начало и конец буфера sed соответственно. Напомню, позицией совпадения в sed считается позиция между символами, потому, $ это позиция между последним символом и концом строки. Если символов вообще нет, то $ совпадает с ^.
Имеются ещё два якоря: «\`» и «\'». Так же как и предыдущие, эти якоря задают начало и конец буфера sed. Различие между ними проявляется тогда, когда в буфере несколько строк. Мы рассмотрим их ниже.
В в заключение этой главы я хочу отметить, что написал я тут так мало вовсе не потому, что тема эта такая незначительная, нет! Просто про регулярные выражения и мною и без меня написано уже слишком много, повторять мне не хочется, в вашей ОС есть множество документации по RE, и её всегда можно найти даже без интернета, если у вас есть даже самый поганый выход в сеть, вы всегда сможете найти то-же самое ещё и по-русски! Регулярные выражения страшны и непонятны только первое время, уверяю вас, уже через недельку активного использования, вы не только станете их понимать, но и даже почувствуете красоту некоторых формул!
Удачи!
Чуть выше был пример с ошибкой. В чём-же заключается ошибка? Давайте попробуем:
$ echo "ABC-234XYZ+17sss" | sed -r 's/.*([-+]?[0-9]+).*/\1/' 7
Видно, что наше RE захватило всего-лишь последнюю цифру, а не число, как ожидалось. Это не баг, а фича. Далее подробно будет рассказано, почему так, и как добиться ожидаемого поведения. Сейчас только замечу, что выражения с квантификаторами sed делит на части, и ищет их все в строке, например здесь выражение делится на 4 части:
/.*/
/[-+]?/
/[0-9]+/
/.*/
Совпадение (а значит и замена) будет тогда, и только тогда, когда будут найдены все четыре части. Причём каждая часть ищется независимо и параллельно одна от другой. Причём каждое выражение захватывает сколько сможет. Выражение, которое стоит вначале, захватывает больше всех. Вот и в нашем случае, /.*/ пожрало все символы, кроме одной семёрки - одну семёрку ей пришлось оставить, т.к. выражение /[0-9]+/ совпадает только лишь с одним символом(или с несколькими, но не с пустой строкой), и если-бы звёздочка пожрала-бы и семёрку, то совпадения не было-бы вообще.
А вот + (плюс) от 17и был успешно пожран первой звёздочкой, она может это себе позволить, по той причине, что выражение /[+-]?/ может довольствоваться ничем. Вот оно ничто и получило. А именно место между `1' и `7'.
Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).
Вы можете пожертвовать небольшую сумму яндекс-денег на счёт 41001666004238 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.