Глава 6. info sed. (русский перевод).

Содержание

info sed Потоковый редактор.
Начало
Ограничения sed.
Ключи командной строки.
Программирование в sed.
Вступление.
Выбор строк, которые редактирует `sed' (адресация).
Обзор синтаксиса регулярных выражений.
Как `sed' буферизирует данные?
Команды sed
Основные команды(#,q,d,p,n,{},s).
Другие команды(y,a,i,c,=,l,r,w,D,N,P,h,H,g,G,x).
Команды для sed гуру(:,b,t).
Команды специфичные для GNU `sed'(e,L,Q,R,T,v,W)
Команда z
GNU Расширения регулярных выражений.
Список спец-символов.

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

Начало

Замечание

Перевод drBatty

Документация к версии 4.0.9 GNU 'sed', потоковому редактору. Втр 25 Авг 2009 17:02:38

Исправлено для версии 4.2. Сбт Июн 5 10:58:00 MSD 2010

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

  1. загружаем текст
  2. редактируем нужные строки
  3. сохраняем результат

В отличие от них, в sed п2 и п1 идут в обратном порядке, т.е.

  1. Мы пишем скрипт на особом «языке sed», в котором написано КАК надо редактировать текст.
  2. sed выполняет наш скрипт над данным текстом:
    1. sed загружает первую строку из входного потока.
    2. sed выполняет написанный нами скрипт для загруженной строки.
    3. sed выводит строку в выходной поток.
    4. пункты 2.a, 2.b, и 2.c выполняются для всех строк входного потока,
    5. ...кроме случая, когда скрипт указывает иное(например в пункте 2.b возможен случай, когда загрузится следующая строка, и потому, пункт 2.a выполнится не для всех строк текста; кроме того, скрипт в любой момент может завершить работу, и прочее...)

Конечно, обычные задачи редактирования текста выполнять с помощью sed жутко неудобно (хотя и возможно), по той простой причине, что редактировать текст необходимо ДО его загрузки (текста может и не существовать в тот момент, когда мы его редактируем). Однако, с другой стороны, редактирование может происходить в автоматическом режиме и|или пользователем, который слабо понимает, что-же он собственно делает ;-) Кроме того, входные и выходные данные практически для всех команд Linux являются текстами, а значит, мы можем собирать почти любые команды в "сверх-конвейеры", используя в качестве "цемента" команду sed.

В качестве языка программирования в sed применяется достаточно простой специальный язык, в основном заимствованный из старинного редактора ed. Все команды записываются одним символом, при этом большие и малые буквы различаются(n и N это разные команды). В sed имеется всего две переменные:

  1. область редактирования. (далее для краткости буфер), в начале каждого цикла именно в него загружается текущая строка, и обычно в него помещаются результаты работы sed-скрипта. Если не указано иное, в конце каждого цикла обработки строки, содержимое буфера выводится в выходной поток.
  2. область удержания, hold space, далее для краткости буфер2. В буфер2 можно записать(или добавить) буфер, для сохранения его значения между циклами обработки строк. К примеру, в нём можно собрать одну большую строку, из заданного количества исходных строк текста. Конечно возможны и более сложные (и более полезные!) применения буфера2, они ограничиваются только вашей фантазией.

В языке sed доступны операции условных и безусловных переходов, операции с файлами, а так-же вызовы внешних команд(shell).

Доказано, что на языке sed теоретически можно написать ЛЮБУЮ программу, хотя конечно для выполнения многих задач лучше использовать другие языки. Впрочем, для задач обработки текстов, sed более чем достаточно, что позволяет мне отказаться от применения более сложных языков, таких как perl или gawk.

Замечание

Следует помнить, что хотя sed - интерпретируемый ЯП, скорость обработки скриптов во много раз выше чем у других языков (это связанно в основном с тем, что язык sed очень прост, и во многих случаях даже примитивен, интерпретатору не нужно долго думать о назначении каждой команды, т.к. команды всегда размером в 1 символ, кроме того, не надо искать по таблицам переменные - их всего 2, и любая команда жёстко привязана к своим переменным). Хотя сами скрипты sed и интерпретируются, но входящие в них регулярные выражения компилируются(собираются), это позволяет использовать одно и то-же выражение многократно, без повторения его разбора в каждом цикле.

В отличие от более сложных языков включающих регулярные выражения(perl, php, etc...), в sed нет регулировки "жадности" выражений - регулярные выражения в sed всегда жадные. С одной стороны, это неудобно для кодера, так-как ограничивает его свободу построения регулярных выражений, с другой стороны, мне ещё ни разу не встречалось выражение, которое нельзя было-бы переписать для sed, а разных выражений я написал довольно много... Конечно, я допускаю, что можно придумать такое выражение, и возможно даже задачу, в котором оно необходимо, однако на практике такого мне не попадалось. Не отключаемая жадность выражений в sed позволяет добиться более высокой скорости, чем в других языках (ценой несколько более высокого расхода мозгового вещества кодера :-) ).

Простота языка sed позволяет использовать его даже без всякого изучения! Если вы понимаете, что такое regexp вы можете писать скрипты для sed сразу, просто просмотрев по диагонали man sed. Эта простота сыграла с sed злую шутку: большинство пользователей полагают, что sed это всего-лишь тупой фильтр, способный заменить в тексте одно слово на другое, и не более. Это не так - несмотря на многие ограничения sed, это действительно мощный ЯП, позволяющий во многих случаях решить задачу самым быстрым способом. (быстрым как по времени выполнения скрипта, так и по времени его разработки).

Во многих(если не в большинстве) случаях, применение ЯП низкого уровня (вроде C) не даёт существенного повышения быстродействия, за то приводит к большим сложностям при разработке, кроме того, конечному пользователю в большинстве случаев придётся ещё и пересобрать программу для своей системы (это в лучшем случае!), применение-же других ЯП высокого уровня часто не оправдано (хеш таблицы на 100 переменных и связанные списки - отличная штука, вот только далеко не всегда это надо).

Кроме того, применяя более мощные интерпретируемые языки (вроде perl'а) мы сильно теряем в скорости. Если-же нам, для выполнения нашей задачи, действительно нужны сложные структуры данных (например базы данных), то встроенные в языки высокого уровня нам мало помогут - они оптимизированы для некоторого класса задач, и обычно нам не сильно подходят, сменить их нет никакой возможности - если в php массивы являются hash таблицами - то это так и останется, надо нам это или нет, поменять структуру массива на, к примеру, 2-3 дерево, или даже на простейший линейный массив нет никакой возможности.

То, что в sed нету никаких структур данных и даже никакой возможности их организации является скорее достоинством - я с лёгкостью прикручивал к sed-скрипту работу с MySQL базой данных, это даже проще, чем интегрировать БД в php, я уж не говорю о C++. А сравнивать скорость и мощность MySQL со встроенными массивами какого-нибудь perl'а просто глупо.

Дальнейший текст представляет собой слегка обработанный перевод info sed. Обработка связанна с тем, что я говорю и пишу не по английски, потому я подчеркну некоторые моменты, которые слабо волновали англоговорящих кодеров - им проще, каждая их буква занимает диапазон [a-zA-Z], и всегда укладывается в один байт, и более того! В этом байте старший бит всегда равен 0. Для русского языка это, к сожалению, не так: в русском языке применяется множество кодировок, причём почти все они используются на практике. Мало того, в кодировках UTF* одна русская буква занимает два байта - в этом случае работа утилиты sed существенно усложняется. К счастью, в современных версиях(уже лет 10 как) эти различия и особенности локализаций учтены, и потому утилита sed прекрасно справляется с обработкой в т.ч. и текстов на русском языке, хотя при работе с ними нужно учитывать некоторые тонкие моменты.

Кроме того, я ещё и проверял примеры на практике, причём позволил себе некоторые из них незначительно изменить. Например, лично я не люблю базовые регулярные выражения, и предпочитаю расширенные. Спор о том, какие из них лучше похож на спор о том, с какой стороны нужно разбивать яйцо :-) Мне кажется, что удобнее расширенные, т.к. мне попадаются выражения, которые короче записываются именно как ERE, хотя в вашем случае это может быть и не так.

В оригинале, авторы используют базовые RE, я-же часто буду использовать ERE, к тому-же, в примерах, это более наглядно. В любом случае, разница между базовыми и расширенными выражениями всего-лишь в форме записи, не смотря на название, расширенные regexp'ы не дают никаких новых возможностей по сравнению с базовыми (скорее наоборот - могут быть непереносимыми, и требуют дополнительных опций(см. опцию -r)).

Возможно, ERE в чём-то и полезны, например отучают от привычки некоторых sed-гуру писать «*», там, где по смыслу положено писать «\+» (в ERE всё равно, «*» или «+», в обычных RE плюс ещё надо за экранировать. Понятно, что sed скрипты пишут только ленивые люди, потому им лень ставить \. Не ленивые правят свои тексты в блокноте, и про sed не в курсе).

Ограничения sed.

У sed существуют сильные ограничения, о них надо всегда помнить:

  1. Утилита sed предназначена для работы с текстом, не следует использовать её для работы с двоичными данными. Если-же вам всё-таки это нужно, то вам придётся решать 3 проблемы:
    1. В каждом цикле, sed загружает 1 строку в оперативную память компьютера, строкой в sed считается последовательность любых байтов до символа «\n» (включительно) в текстах строки всегда имеют небольшую длину (обычно меньше 100 байтов), и с учётом этого факта, и производилось оптимизация кода sed.

      Замечание

      Текущая версия sed (4.2, 2009й год) выделяет 50 байтов для строки. Если строка не влезает в 50 байтов, то область памяти удваивается. Это - не самое быстрое действие. Потому тексты в которых строки не длиннее 50 байт обрабатываются быстрее всего. Несколько медленнее обрабатываются тексты, в которых строки хоть и больше 50 байт, но не слишком длинные, к примеру тексты со строками не более 256 байт.
      Попытка обработать текст, в котором встречались строки размером в десятки мегабайт привела к тому, что сначала потратилась вся память (я экспериментировал на старом компьютере, тут памяти всего 32Мб, и 60Мб свопа), затем заполнился весь своп (в этот момент всё жутко затормозилось), а затем ОС убила саму sed, и ещё плеер, который играл в этот момент музыку из mp3... Вы можете сказать - "У меня не такой древний комп!", ну и что? На более новых компах не только память больше, но и файлы длиннее... Что поделать, команда find / занимает примерно одинаковое время, что на этой рухляди в 133+MHz, что на моём сервере 3000MHz а именно - дохрена времени...
    2. Многие выражения будут работать совсем не так, как ожидается, рассмотрим например одно из самых простых (и часто употребляемых) RE как ".*", предполагается, что это RE совпадает с любым количеством любых символов, т.к. sed обрабатывает символы по строкам, то ожидается, что ".*" совпадёт с любой строкой, причём целиком.

      Это верно, но только для СИМВОЛОВ. Оказывается, не все байты являются символами, я сам с удивлением узнал, что некоторые байты sed считает НЕСИМВОЛАМИ! Поэтому, команда "s/.*//" вовсе НЕ очищает любую строку, она работает не так, как ожидается: стираются только первые СИМВОЛЫ, до первого НЕСИМВОЛА, и этот "хвост" начинающийся с первого НЕСИМВОЛА так и остаётся в строке. Какие байты считаются СИМВОЛАМИ, а какие НЕСИМВОЛАМИ зависит от применяемой кодировки.

      Про несимволы и сложности с ними см. также.

    3. Регулярное выражение "/../" безусловно полностью совпадает с любыми строками из двух символов, и частично совпадает со строками длиннее 2х символов ("частично совпадает" в данном случае значит, что совпадает, но ещё остаётся некоторые символы).

      Но кто вам сказал, что это RE совпадает с любыми двумя байтами? Кроме того (как я уже отметил в пункте b), что некоторые байты не являются СИМВОЛАМИ, есть ещё один момент - символ вовсе не обязательно содержит ровно 1 байт. В русском языке, в UTF-8 буквы размером 2 байта, а в китайском - даже 5!

    Подсказка

    Учитывая всё вышесказанное, для работы с двоичными данными необходимо прежде всего отключить локализацию: установить переменные LC_*, а так-же LANG в значение "C".
  2. То, что на всё про всё нам дано всего 2 переменных сильно ограничивает полёт фантазии - слишком сложные алгоритмы не получится реализовать sed скриптом - для этого есть другие языки. Не слишком удачно реализованы команды условных переходов, они позволяют кодировать только самые простые ветвления и циклы. Впрочем и с этим можно многое сделать - мне удавалось создать sed-скрипты с поиском по словарю, с реализацией стека, и даже скрипт, который работает как счётчик(правда в двоичной системе)(см сл. пункт).
  3. sed не умеет считать. Вообще. Это наверное единственный ЯП, в котором нет арифметики. Если вам понадобились вычисления - готовьтесь подключать внешние утилиты. То, что я смог реализовать счётчик средствами sed - исключение, которое только подтверждает правило. Даже номера строк утилите sed не по зубам :-( Эти номера могут быть использованы только в двух случаях:
    1. Для адресации (ниже представлены примеры и описание).
    2. можно ещё вывести номер строки в выходной поток, жаль что нельзя в буфер. Даже для того, что-бы просто записать перед каждой строкой её номер (вроде cat-n) приходится выводить номера строк одной командой sed, а потом, ловить номера и строки другой sed, которая их и склеивает, вроде этого:
      $ sed '=' Test.txt | sed 'N; s/\n/ /'"
    Впрочем, это всё можно обойти - во первых используя конвейер, как в прошлом примере, во вторых можно вызвать средствами sed специализированные утилиты, для обработки ваших чисел. Конечно, тут сложно контролировать возникновение ошибок, но их просто избежать, если заранее, средствами самой sed, проверить входные(для внешней команды) данные. Например в ERE
    /[0-9]{1,3}/
    нельзя впихнуть ничего, кроме десятичного положительного целого в диапазоне 0...999; а в ERE
    /[[:xdigit:]]{32}/
    может войти только MD4/5 хеш, причём,хотя для самого хеша и допустимы другие шаблоны(например под мой не подходит допустимый хеш "Ac46"), но все другие значения сигнализируют о ошибке: их никогда не будет от программ вычисления этих хешей, а с другой стороны, сомневаюсь, что программы обработки хешей поймут такой "нестандарт"...
  4. Следует учитывать, что sed это всё-таки интерпретатор - скрипт будет выполнятся для каждой строки. Если действия в скрипте сложные, то попробуйте вынести неизменную часть в начало скрипта, в блок с адресом 1, тогда он будет выполнятся только для первой строки. Если это невозможно - оформите это отдельным скриптом (необязательно sed-скриптом, или даже вообще не скриптом), и передайте sed перед текстом. Если и это не получается - стОит подумать о смене языка :( Не забывайте, что современные компилирующие ЯП (точнее, современные компиляторы) сами выносят неизменные части из циклов, во многих случаях, даже эффективнее чем это сделал-бы программист(что поделать, программист не знает, и не может знать возможностей и ограничений железа и ОС, под которыми будет работать его программа).

    Замечание

    В исходниках sed есть и компилятор. Однако, думаю что для 90% применений его использование не имеет смысла: наибольшая нагрузка приходится на чтение файлов и на регулярные выражения. Потому компиляция самого sed-скрипта не приведёт ни к какому выигрышу по производительности. Впрочем - вы можете попробовать...

    Внимание

    Кстати, это решит многие проблемы безопасности - например вы можете установить атрибуты «скрипта» как 4711, что позволит юзерам ТОЛЬКО запускать эту программу, причём она будет работать с правами владельца, например рута, а юзеры не смогут не только изменить эту программу, но даже прочитать её.
  5. Сам принцип работы sed предполагает, что мы ТОЧНО знаем ЧТО делать, но у нас нет того С ЧЕМ мы будем это делать (часто того, С ЧЕМ будет работать sed вообще не существует). Если ситуация противоположная (например мы решили исправить программу на C, но сами ТОЧНО ещё не знаем как - надо посмотреть как она реализована, и немного(или много) её подправить) тогда использование sed - плохая идея. В этом случае найдите себе хороший текстовый редактор.
  6. Учтите, для многих задач уже существуют готовые модули для некоторых языков. Если для вашей задачи уже есть нужный модуль на perl'е, может есть смысл писать всё на perl?
  7. Наконец, команду sed большинство не понимает, а стало-быть - боятся и не любят :-( Если в вашем скрипте встретилось что-либо сложнее, чем "sed 's/XXX/YYY/'" - будьте готовы к вопросам и непониманию... С другой стороны, в Linux намного чаще приходится править чужие скрипты, чем писать свои, а в них часто встречаются довольно хитрые конструкции, в т.ч. и с sed, потому, разобраться с sed придётся, нравится вам это, или нет... К счастью, научится sed - как научится кататься на велосипеде: вы никогда уже не разучитесь. Это не WinAPI, в котором имя нужной функции помнишь всего 5 минут после изучения MSDN. (Для тех кто не в курсе: в MSDN описаны тысячи разных и необходимых функций, при этом длинна их имён составляет 10-20 букв, запомнить их практически невозможно. В sed всего 3 десятка команд, это в состоянии вызубрить даже законченный склеротик).

Замечание

Далее идут примеры, для многих из них нужен входной файл. Примеры проверенные, и они работают, однако, если запускать некоторые из них, вы получите просто приглашение: Что вы хотите - это всё-таки редактор - дайте же что-нибудь отредактировать! Ну хотя-бы
$ seq 1 10 >num.txt

Ключи командной строки.

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

`-V' `--version'
Вывод версии, информации о копирайте, и выход.
`-h' `--help'
Вывод краткой справки о ключах командной строки, и выход.
`-n' `--quiet' `--silent'
По умолчанию, sed выводит обработанный буфер для каждой строки, при использовании этой опции вывод подавляется, кроме явного указания (команды p; s///p; w /dev/stdout ).
`-i[SUFFIX]' `--in-place[=SUFFIX]'

Эта опция используется для редактирования файла "на месте", т.е. вместо проталкивания файла через фильтр, он редактируется как в обычных текстовых редакторах. GNU sed создаёт временный файл, в который выводит stdout, после чего исходный файл удаляется, а на его место копируется созданный временный(если не задан SUFFIX, если он задан, то старый файл не удаляется, а переименовывается в ${OLD_NAME}SUFFIX). Имеется следующее расширение: если в SUFFIX нет символов *, то SUFFIX прибавляется к концу имени файла, иначе, * заменяется на имя исходного файла. Например:

$ sed '' test.txt -ix/*~

Эта команда редактирует файл test.txt, а так-же сохраняет исходную копию в каталоге x, под именем test.txt~.

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

Если каталога x не существует, то исходная копия нигде не сохранится, и никаких сообщений об ошибке не будет!

Замечание

Подробности реализации: моя версия(4.0.9) создаёт временный файл в том-же каталоге, где лежит исходный (под именем sed??????, где ?????? - шесть уникальных символов), если такой файл создать невозможно, sed прерывает работу с сообщением об ошибке. После окончания редактирования, sed переименовывает исходный файл в соответствии с шаблоном SUFFIX, либо уничтожает исходный файл, если SUFFIX не задан, или создание невозможно. Если создание временного файла невозможно, то работа sed прерывается с ошибкой, при этом, никаких действий не совершается.

Внимание

Следует учитывать, что с опцией -i возможно «редактирование» даже файлов, у которых сброшено право модификации. Дело в том, что мы вовсе не редактируем файл, мы создаём новый, а потом переименовываем его так, что-бы он стал старым. При этом старая копия уничтожается, или переименовывается (при использовании SUFFIX'а). Такое поведение очень удобно если вы - злоумышленник, и вам требуется подчистить следы своего пребывания в чужой системе (при этом не забудьте установить атрибут s на старом файле, это приведёт к тому, что старая копия будет удалена без всякой возможности восстановления). Во всех других случаях, такое поведение представляет собой серьёзную брешь в безопасности нашей системы...

Подробнее об этом "баге" мозжно посмотреть здесь.

Эта опция включает опцию -s (см. ниже).

`-l N' `--line-length=N'
Установка длинны для принудительной разбивки строк командой 'l'. Если N равно 0(нулю), строки никогда не разбиваются, если ключ -l не задан, Строки разбиваются по 70 символов.
`-r' `--regexp-extended'
Использование расширенных регулярных выражений (extended regular expressions, ERE). Расширенные регулярные выражения отличаются от обычных тем, что многие символы имеют в них специальное значение, и не требуют экранирование их обратным слешем. Например: обычное выражение:
/\([0-9]\+\.\)\{3\}/
расширенное:
/([0-9]+\.){3}/
Это GNU расширение, и оно может быть непереносимо.
`-s' `--separate'
По умолчанию, sed считает все переданные ей файлы одним большим файлом, и нумерует их строки последовательно(т.е. если в первом файле 100 строк, то первая строка второго файла будет иметь номер 101). С этим ключом sed нумерует строки каждого файла отдельно, потому первая строка каждого файла будет иметь номер 1, а последняя '$'.
`-u' `--unbuffered'
Почти полное отключение буферизации как входного, так и выходного потока, размер буфера будет не более необходимого размера.
`-e SCRIPT' `--expression=SCRIPT'
Добавляет в командную строку SCRIPT, так-как первым параметром и так является скрипт, то этот ключ пригодится только при использовании ключа -f, или если необходимо одной командой выполнить несколько скриптов.
`-f SCRIPT-FILE' `--file=SCRIPT-FILE'

Заставит выполнить SCRIPT-FILE, как последовательность команд sed.

Если ключи -f и -e не используются, скриптом считается первый параметр не являющийся ключом.

Файл "-" является стандартным потоком ввода, из него-же читается ввод, если никаких файлов не задано.

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

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