Содержание
Документация к версии 4.0.9 GNU 'sed', потоковому редактору. Втр 25 Авг 2009 17:02:38
Исправлено для версии 4.2. Сбт Июн 5 10:58:00 MSD 2010
sed это потоковый редактор. Потоковый редактор используется для базовых преобразований текста из входного потока(из файла, или из конвейера). Отличие sed от других редакторов заключается в том, что в других редакторах мы:
В отличие от них, в sed п2 и п1 идут в обратном порядке, т.е.
Конечно, обычные задачи редактирования текста выполнять с помощью sed жутко неудобно (хотя и возможно), по той простой причине, что редактировать текст необходимо ДО его загрузки (текста может и не существовать в тот момент, когда мы его редактируем). Однако, с другой стороны, редактирование может происходить в автоматическом режиме и|или пользователем, который слабо понимает, что-же он собственно делает ;-) Кроме того, входные и выходные данные практически для всех команд Linux являются текстами, а значит, мы можем собирать почти любые команды в "сверх-конвейеры", используя в качестве "цемента" команду sed.
В качестве языка программирования в sed применяется достаточно простой специальный язык, в основном заимствованный из старинного редактора ed. Все команды записываются одним символом, при этом большие и малые буквы различаются(n и N это разные команды). В sed имеется всего две переменные:
В языке 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 существуют сильные ограничения, о них надо всегда помнить:
Многие выражения будут работать совсем не так, как ожидается, рассмотрим например одно из самых простых (и часто употребляемых) RE как ".*", предполагается, что это RE совпадает с любым количеством любых символов, т.к. sed обрабатывает символы по строкам, то ожидается, что ".*" совпадёт с любой строкой, причём целиком.
Это верно, но только для СИМВОЛОВ. Оказывается, не все байты являются символами, я сам с удивлением узнал, что некоторые байты sed считает НЕСИМВОЛАМИ! Поэтому, команда "s/.*//" вовсе НЕ очищает любую строку, она работает не так, как ожидается: стираются только первые СИМВОЛЫ, до первого НЕСИМВОЛА, и этот "хвост" начинающийся с первого НЕСИМВОЛА так и остаётся в строке. Какие байты считаются СИМВОЛАМИ, а какие НЕСИМВОЛАМИ зависит от применяемой кодировки.
Про несимволы и сложности с ними см. также.
Регулярное выражение "/../" безусловно полностью совпадает с любыми строками из двух символов, и частично совпадает со строками длиннее 2х символов ("частично совпадает" в данном случае значит, что совпадает, но ещё остаётся некоторые символы).
Но кто вам сказал, что это RE совпадает с любыми двумя байтами? Кроме того (как я уже отметил в пункте b), что некоторые байты не являются СИМВОЛАМИ, есть ещё один момент - символ вовсе не обязательно содержит ровно 1 байт. В русском языке, в UTF-8 буквы размером 2 байта, а в китайском - даже 5!
-n
) приходится выводить номера строк одной командой sed, а потом, ловить номера и строки другой sed, которая их и склеивает, вроде этого:
$ sed '=' Test.txt | sed 'N; s/\n/ /'"
/[0-9]{1,3}/нельзя впихнуть ничего, кроме десятичного положительного целого в диапазоне 0...999; а в ERE
/[[:xdigit:]]{32}/может войти только MD4/5 хеш, причём,хотя для самого хеша и допустимы другие шаблоны(например под мой не подходит допустимый хеш "Ac46"), но все другие значения сигнализируют о ошибке: их никогда не будет от программ вычисления этих хешей, а с другой стороны, сомневаюсь, что программы обработки хешей поймут такой "нестандарт"...
$ seq 1 10 >num.txt
sed может использоваться со следующими ключами командной строки:
`-V' `--version'
`-h' `--help'
`-n' `--quiet' `--silent'
`-i[SUFFIX]' `--in-place[=SUFFIX]'
Эта опция используется для редактирования файла "на месте", т.е. вместо проталкивания файла через фильтр, он редактируется как в обычных текстовых редакторах. GNU sed создаёт временный файл, в который выводит stdout, после чего исходный файл удаляется, а на его место копируется созданный временный(если не задан SUFFIX
, если он задан, то старый файл не удаляется, а переименовывается в ${OLD_NAME}SUFFIX). Имеется следующее расширение: если в SUFFIX нет символов *, то SUFFIX прибавляется к концу имени файла, иначе, * заменяется на имя исходного файла. Например:
$ sed '' test.txt -ix/*~
Эта команда редактирует файл test.txt, а так-же сохраняет исходную копию в каталоге x, под именем test.txt~.
Следует учитывать, что с опцией -i
возможно «редактирование» даже файлов, у которых сброшено право модификации. Дело в том, что мы вовсе не редактируем файл, мы создаём новый, а потом переименовываем его так, что-бы он стал старым. При этом старая копия уничтожается, или переименовывается (при использовании SUFFIX'а). Такое поведение очень удобно если вы - злоумышленник, и вам требуется подчистить следы своего пребывания в чужой системе (при этом не забудьте установить атрибут s на старом файле, это приведёт к тому, что старая копия будет удалена без всякой возможности восстановления). Во всех других случаях, такое поведение представляет собой серьёзную брешь в безопасности нашей системы...
Подробнее об этом "баге" мозжно посмотреть здесь.
Эта опция включает опцию -s
(см. ниже).
`-l N' `--line-length=N'
`-r' `--regexp-extended'
/\([0-9]\+\.\)\{3\}/расширенное:
/([0-9]+\.){3}/Это GNU расширение, и оно может быть непереносимо.
`-s' `--separate'
`-u' `--unbuffered'
`-e SCRIPT' `--expression=SCRIPT'
-f
, или если необходимо одной командой выполнить несколько скриптов.
`-f SCRIPT-FILE' `--file=SCRIPT-FILE'
Заставит выполнить SCRIPT-FILE, как последовательность команд sed.
Если ключи -f
и -e
не используются, скриптом считается первый параметр не являющийся ключом.
Файл "-" является стандартным потоком ввода, из него-же читается ввод, если никаких файлов не задано.
Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).
Вы можете пожертвовать небольшую сумму яндекс-денег на счёт 41001666004238 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.