Команды обработки текстов

Алексей Федорчук
2001-2005 гг

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

Вступление

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

Однако круг объектов таких команд не столь уж узок, как может показаться. Ведь именно в виде обычных текстовых файлов в ОС POSIX-семейства хранится масса общесистемной информации, исполняемых сценариев, баз данных атрибутов самых разных объектов. Далее — собственно нарративные тексты любого содержания: ведь чисто текстовый формат для них куда роднее, чем всякого рода *.doc и *rtf. Ну и никем не возбраняется использовать такие команды в отношении текстов с разметкой — HTML ли, XML, TeX или еще чего. Так что поле приложения рассматриваемых команд — достаточно обширно.

Просмотр файлов

Однако прежде чем как-то манипулировать контентом файлов, желательно этот самый контент некоторым образом просмотреть. И тут можно вспомнить о команде cat, посредством которой мы некогда создавали файлы. Данная с именем файла в качестве аргумента, она выведет его содержимое на экран. Можно использовать и конструкцию перенаправления:

$ cat < filename

Не смотря на то, что в принципе это разные вещи, результат будет тот же — вывод содержимого файла на экран.

Недостаток команды cat как средства просмотра — невозможность перемещения по телу файла: выведя содержимое, она завершает свою работу. Конечно, «пролистывание» выведенного возможно, но — средствами системной консоли, а не самой команды.

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

В Unix-системах имеется две основные программы pager’а — more и less. Первая из них допускает только однонаправленный (вперед) просмотр и имеет слабые интерактивные возможности. Почему ныне и представляет лишь исторический интерес, так что о ней я говорить не буду. Тем более, что в современных свободных POSIX-системах она как таковая отсутствует: файл с именем /usr/bin/more, который можно обнаружить во FreeBSD и некоторых дистрибутивах Linux, на самом деле представляет собой жесткую или символическую ссылку на ту же самую программу, что и команда less. Хотя эта программа проявляет несколько различные свойства, в зависимости от того, какой из указанных команд она вызвана, функции ее от этого не меняются. Так что дальше я буду говорить только о команде less.

Самый простой способ вызова команды

$ less filename

после чего на экран выводится содержимое файла, указанного в качестве аргумента, по которому можно перемещаться в обоих направлениях, как построчно, так и постранично. В нижней строке экрана можно видеть символ двоеточия — приглашения для ввода команд различного назначения. В частности, нажатие клавиши h выводит справку по использованию less, а клавиши q — выход из программы просмотра (или из просмотра справочной системы, если она была перед этим вызвана). Если команда была вызвана как more (это достигается еще и специальной опцией — less -m), вместо символа двоеточия в нижней строке будет выведено имя файла с указанием процента просмотра:

command.txt 3%

что, однако, не воспрещает и здесь давать ее встроенные команды — вводом символа двоеточия (:) и закрепленной за командой литеры (или их сочетания).

Большинство встроенных команд less предназначено для навигации по телу файла. Осуществляется она несколькими способами:

  • с помощью стандартных клавиш управления курсором: PageDown или Spacebar (вперед на один экран), PageUp (назад на один экран), Down или Enter (вперед на одну строку), Up (назад на одну строку), Right (на пол-экрана вправо), Left (на пол-экрана влево);
  • с помощью предопределенных клавишных комбинаций, сходных с управляющими клавиатурными последовательностями командных оболочек и таких текстовых редакторов, как emacs и joe (хотя и не всегда с ними совпадающими): Control+V (на один экран вперед), EscapeV (на один экран назад), Control+N (на одну строку вперед), Control+P (на одну строку назад);
  • с помощью фиксированных символьных клавиш, иногда подобных таковым командного режима редактора vi: z и w (вперед и назад на один экран), e и y (вперед и назад на одну строку, можно использовать также привычные по vi клавиши j и k, соответственно), d и u (вперед и назад на пол-экрана).

Последний способ интересен тем, что допускает численные аргументы перед символьной командой: так, нажатие 3e приведет к перемещению на три строки вперед, а 2w — на два экрана назад.

Помимо «плавной», так сказать, навигации, можно перемещаться по файлу и скачками (jumping): нажатие клавиши с символом g (или последовательности Escape<) позволяет переместиться к первой строке файла, клавиши G (регистр важен! дублирующий вариант — Escape>) — к последней его строке, клавиши p — к началу файла.

Кроме навигации, имеется и возможность двустороннего поиска — в направлении как конца, так и начала файла. Для поиска вперед требуется ввести символ прямого слэша (/) и за ним — искомое сочетание символов. Поиск в обратном направлении предваряется символом вопроса (?). В обоих случаях в шаблоне поиска можно использовать стандартные регулярные выражения *, ?, [список_символов] или [диапазон_символов]. Нажатие клавиши n (в нижнем регистре) приводит к повторному поиску в заданном ранее направлении, клавиши N (в верхнем регистре) — к поиску в направлении противоположном.

Управляющие комбинации команды less могут быть переопределены с помощью команды lesskey. Формат ее

$ lesskey -o output input

В качестве входных данных выступает простой текстовый файл (по умолчанию — ~/.lesskey, однако его следует создать самостоятельно), описывающий клавишные последовательности в следующем, например, виде:

#command
r        forw-line
n        forw-line
...
k         back-line
...

Выходные данные — создаваемый из текстового двоичный файл, который собственно и используется командой less. Стандартное для него имя — ~/.less.

Команда less допускает одновременный просмотр нескольких файлов. Для этого ее следует вызвать в форме

$ less file1 file2 ... file#

после чего между открытыми файлами можно переключаться посредством :n (к следующему файлу), :p (к предыдущему файлу), :x (к первому файлу). Путем нажатия :d текущий файл исключается из списка просмотра. Символ двоеточия во всех этих случаях вводится с клавиатуры в дополнение к приглашению на ввод команд.

Команда less имеет великое множество опций — описание их на странице экранной документации занимает более дюжины страниц, поэтому задерживаться на них я не буду. Следует заметить только, что опции эти могут использоваться не только в командоной строке при запуске less, но и интерактивно — после символа дефиса в приглашении ввода. Так, указав там -m, можно включить т.н. промежуточный формат приглашения (с отображением процентов просмотренного объема файла), а с помощью -M — длинный (more-подобный) формат, при котором в приглашении дополнительно указываются имя файла, его положение в списке загруженных файлов, просматриваемые ныне строки:

command.html (file 2 of 10) lines 1-29/1364 2%

Значение команд постраничного просмотра файлов еще и в том, что именно с их помощью осуществляется доступ к экранной документации (man-страницам). Команда

$ man cmd_name

как было описано в предыдущей интермедии, на самом деле вызывает определенный по умолчанию pager для просмотра соответствующего файла /usr/share/man/man#/cmd_name.gz. Какой именно — определяется переменной PAGER в пользовательских настройках.

Кроме команд постраничного просмотра, существуют команды для просмотра фрагментарного. Это — head и tail, выводящие на экран некоторую фиксированную порцию файла, указанного в качестве их аргумента, с начала или с конца, соответственно. По умолчанию эта порция для обеих команд составляет десять строк (включая пустые). Однако ее можно переопределитьg произвольным образом, указав опции -n [число_линий] или -c [количество_байт]. Например, команда

$ head -n 3 filename

выведет три первые строки файла filename, а команда

$ tail -c 100 filename

его последние 100 байт. При определении выводимого фрагмента в строках название опции (n) может быть опущено — достаточно числа после знака дефиса.

Существуют и средства просмотра компрессированных файлов. Для файлов, сжатых программой gzip, можно использовать команды zcat и zmore, для спрессованных командой bzip2 — команду bzcat. Использование их ничем не отличается от аналогов для несжатых файлов — в сущности, именно они и вызываются для обеспечения просмотра. В случае команды zmore, как нетрудно догадаться, на самом деле используется команда less (сама по себе она аналога для компрессированных файлов не имеет).

Следующая важная группа операций над контентом файлов — сравнение файлов по содержанию и различные формы объединения файлов и их фрагментов. Начнем со сравнения. Простейшая команда для этого — cmp в форме

$ cmp file1 fil2

производит построчное сравнение файлов, указанных как первый и второй аргументы (а более их и не предусмотрено, все указанное после второго аргумента игнорируется). В случае идентичности сравниваемых файлов не происходит ничего, кроме возврата приглашения командой строки. Если же между файлами имеются различия, выводится номер первого различающегося символа и номер строки, в которой он обнаружен:

file1 file2 differ: char 27, line 4

Это означает, что различия между файлами начинаются с 27-го от начала файла символа (включая пробелы, символы конца строк и т.д.), который имеет место быть в строке 4. С помощью опций -l и -z можно заставить команду cmp вывести номера всех различающихся символов в десятичном или шестнадцатеричном формате, соответственно.

Более информативный вывод обеспечивает команда diff. Она также осуществляет построчное сравнение двух файлов, но выводит список строк, в которых обнаружены отличия. Например, для двух файлов вида

$ less file1
line 1
line 2
line 3
line 4
line 5

и

$less file2
line 1
line 2
line 3
line 3a
line 4
line 5

это будет выглядеть следующим образом:

$ diff file1 file2
3a4
> line 3a

Если различия будут выявлены более чем в одной строке, для каждого расхождения будет выведен аналогичный блок. Смысл его — в том, какие строки первого файла должны быть преобразованы, и как именно, для того, чтобы файлы стали идентичными. Первая линия блока вывода содержит номер строки первого файла, подлежащей преобразованию, номер соответствующей строки второго файла и обозначенное буквенным символом преобразование, во второй линии приведена собственно строка — предмет преобразования. Символы преобразования — следующие:

  • a (от append) указывает на строку, отсутствующую в первом файле, но присутствующую во втором;
  • c (от change) фиксирует строки с одинаковым номером, но разным содержанием;
  • d (от delete) определяет строки, уникальные для первого файла.

То есть в данном примере для преобразования file1 в file2 в него после строки 3 должна быть вставлена строка 4 из второго файла, что символизирует вторая линия блока — > line 3a, где > означает строку из первого сравниваемого файла. Если же аргументы команды diff дать в обратном порядке, вывод ее будет выглядеть следующим образом:

$ diff file2 file1
4d3
< line 3a

показывающим, что для достижения идентичности из file2 должна быть удалена четвертая строка (<>, где < означает строку из второго файла). Если же произвести сравнение file1 с file3, имеющим вид

$ less file3
line 1
line 2
line 3a
line 4
line 5

то вывод команды

$ diff file1 file3
3c3
< line 3
---
> line 3a

будет означать необходимость замены третьей строки из file1 (символ <) на третью строку из file3 (символ >).

Такая форма вывода команды diff называется стандартной. С помощью опции -c можно задать т.н. контекстную форму вывода, при которой на экран направляется не только различающиеся строки, но и строки, их окружающие (то есть контекст, в котором они заключены):

diff -c file1 file2
*** file1       Sun May 12 11:44:44 2002
--- file2       Mon May 13 15:17:27 2002
***************
*** 1,5 ****
--- 1,6 ----
 line 1
 line 2
 line 3
+ line 3a
 line 4
 line 5

Количество строк контента задается опцией -C:

diff -C 1 file1 file2                                      ttyv1
*** file1       Sun May 12 11:44:44 2002
--- file2       Mon May 13 15:17:27 2002
***************
*** 3,4 ****
--- 3,5 ----
 line 3
+ line 3a
 line 4

В этом примере значение опции -C (единица) предписывает вывод по одной строке контекстного окружения вокруг различающейся строки. Сами же различающиеся строки помечаются следующим образом: знаком - (минус, или дефис) — строки, подлежащие удалению из первого файла, знаком + (как в примере) — строки, которые должны быть добавлены, знаком ! — просто различающиеся строки.

Кроме контекстного формата, используется еще и вывод в унифицированном формате, что предписывается опцией -U [значение], в качестве значения указывается число строк. В нем для обозначения изменяемых строк используются только символы + и -, а сам вывод чуть короче, чем при использовании контекстного формата.

С помощью многочисленных опций команды diff сравнение файлов может быть детализовано и конкретизировано. Так, опция -b предписывает игнорировать «пустые» символы пробелов и табуляции в конце строк, а опция -w — вообще «лишние» пробелы (и те, и другие обычно имеют случайное происхождение). При указании опции -B игнорируются пустые строки, то есть не содержащие никаких иных символов, кроме перевода каретки; строки с символами табуляции или пробела как пустые не рассматриваются, для их игнорирования требуется опция -w. Благодаря опции -i при сравнении не принимается во внимание различие регистров символов, а опция -I regexp определяет регулярные вырвжения, строки с которыми также игнорируются при сравнении.

В качестве аргументов команды diff (одного или обоих) могут выступать также каталоги. Если каталогом является только один из аргументов, для сравнения в нем отыскивается файл, одноименный второму аргументу. Если же оба аргумента суть каталоги, в них происходит сравнение всех однименных файлов в алфавитном порядке (вернее, в порядке ASCII-кода первого символа имени, разумеется). Благодаря опции -r сравнение файлов может осуществляться и во вложенных подкаталогах.

Вывод команды diff может быть перенаправлен в файл. Такие файлы различия именуются diff-файлами или, применительно к исходным текстам программ, патчами (patches), о которых будет сказано несколько позже. Именно с помощью таких патчей обычно распространяются изменения к программам (дополнения, исправления ошибок и т.д.).

В принципе, команда diff и придумана была именно для сравнения файлов исходников, над которыми ведут работу несколько (в пределе — неограниченное количество, как в случае с Linux) человек. Однако невозбранно и ее использование в мирных целях — то есть для сравнения просто повествовательных текстов. Единственное, что следует понимать при этом абсолютно ясно — то, что diff выполняет именно построчное сравнение. То есть: сравнение последовательностей символов, ограниченных символами конце строки с обеих сторон. И, соответственно, непрерывная абзацная строка в стиле emacs и vi — совсем не то же самое, что строка, образуемая в редакторе joe на границе экрана. Впрочем, это — вопрос, к которому еще не раз придется возвращаться.

Как уже было отмечено, команда diff осуществляет сравнение двух файлов (или — попарное сравнение файлов из двух каталогов). Однако, поскольку Бог, как известно, любит троицу, есть и команда diff3, позволяющая сранить именно три файла, указываемые в качестве ее аргументов. По действию она не сильно отличается от двоичного аналога. И потому изучение ее особенностей предлагается в качестве самостоятельного упражнения приверженцам троичной идеологии.

Существуют и средства для сравнения сжатых файлов. Это — zcmp и zdiff. Подобно командам просмотра, ими просто вызываются соотвествтующие команды cmp и diff. И потому использование их не имеет никаких особенностей.

От вопроса сравнения файлов плавно перейдем к рассмотрению способов их объединения. Для этого существует немало команд, из которых по справедливости первой должна идти команда cat, поскольку именно сие есть ее титульная функция (cat — от concatenation, сиречь объединения). Ранее уже упоминалось, что она способна добавлять информацию со стандартного ввода в конец существующего файла. Однако дело этим не ограничивается. В форме

$cat file1 file2 ... file# > file_all

она создает новый файл, включающий в себя содержимое всех файлов-аргументов (и именно в том порядке, в каком они приведены в командной строке). Операция, казалось бы, нехитрая — однако представьте, сколько действий потребовалось бы в текстовом процессоре (например, в Word’е) для того, чтобы создать синтетический вариант из полутора десятков фрагментов, раскиданных по разным каталогам?

А вот команда patch выступает в качестве диалектической пары для команды diff, именно она вносит в файл те изменения, которые документируются последней. Выглядит эта команда примерно так:

$patch file1 diff_file

в ответ на что последует нечто вроде следующего вывода:

Hmm...  Looks like a normal diff to me...
Patching file file1 using Plan A...
Hunk #1 succeeded at 4.
done

В результате исходная версия file1 будте сохранена под именем file1.orig, а сам он преобразован в соответствие с описанием diff-файла. Возможна и форма

patch < diff_file

В этом случае команда patch попытается сама определить имя файла-оригинала, и, если это ей не удастся, даст запрос на его ввод. Обращаю внимание на символ перенаправления ввода, поскольку если его опустить, имя dif-файла будет воспринято как первый аргумент команды (то есть имя файла-оригинала).

В качестве второго аргумента команды patch могут использоваться dif-файлы не только в стандартном, но и в контекстном или унифицированном формате. Это следует указать посредством опции -c или -u, соответственно.

Сочетание команд diff и patch очень широко используется при внесении изменений в исходные тексты программы. Так, если мы обратимся к коллекции портов FreeBSD, то в большинстве каталогов портированных программ можно обнаружить подкаталог files (например, /usr/ports/editors/joe-devel/files), содержимое которого как раз и есть те патчи, которые должны накладываться на исходные тексты программы для ее корректной сборки, установки и функционирования во FreeBSD. Сам же исходный текст при этом в неизменном, то есть распространяемом разработчиком, виде получается с его сайта (или любого другого сервера, в том числе и каталога distfiles ftp-сервера проекта FreeBSD.

Не менее часто, чем в слиянии, возникает и необходимость в разделении файлов на части. Цели этой служит команда split. Формат ее:

$ split [options] filename [prefix]

В результате исходный файл будет разбит на несколько отдельных файлов вида prefixaa, prefixab и так далее. Значение аргумента prefix по умолчанию — x (то есть без его указания итоговые файлы получат имена xaa, xab и т.д.).

Опции команды split задают размер выходных файлов — в байтах (опция -b) или количестве строк (опция -l). Первой опцией в качестве единицы, кроме байтов, могут быть заданы также килобайты или мегабайты — добавлением после численного значения обозначения k или m, соответственно.

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

$ split -b 1474560 arch_name

она обеспечит разбиение архива на части, какждая из которых может быть записана на стандартную трехдюймовую дискету. А посредством

$ split -b 650m arch_name

архив можно подготовить к записи на носители CD-R/RW. Напомню, что именно командой split создаются файлы дистрибутива FreeBSD. Легко догадаться, что обратное слияние таких фрагментированных файлов можно выполнить командой cat.

Однако этим возможности BSD-реализации команды split не исчерпываются. С помощью опции -p файл может быть разделена на фрагменты, ограниченные строками, сорержащими текст, приведенный в качестве значения шаблона. Так, командой

$ split -p Глава filename chapter-

текст произвольной длинны будет легко разбит на файлы, соответствующие отдельным главам, которым будут присвоены имена chapter-aa и далее. В качестве значения опции -p могут использоваться тэги HTML или символы разметки TeX, а также регулярные выражения.

Linux-реализация команды split таким свойством не обладает. Однако взамен этому в Linux есть команда csplit, именно для разделения файла по шаблону и предназначенная.

Поиск в файлах

В одном из предыдущих разделов говорилось о поиске файлов посредством команды find. Ныне же речь пойдет о поиске внутри файлового контента — то есть поиске текстовых фрагментов. Для этого в POSIX-системах используется семейство утилит grep — собственно grep, egrep и fgrep, несколько различющихся функционально. Впрочем, в большинстве систем все это суть разные имена (жесткие ссылки) одной и той же программы, именуемой GNU-реализацией grep, включающей ряд функций, свойственных ее расширенному аналогу, egrep. Соответственно, поиск текстовых фрагментов в файлах может быть вызван любой из этих команд, хотя в каждом случае функциональность их будет несколько различаться.

Однако начнем по порядку. Самой простой формой команды grep является следующая:

$ grep pattern files

где pattern — искомая последовательность символов, а files — файлы, среди которых должен производиться поиск (или — просто одиночный файл). В указании имен файлов допустимы обычные маски, например, командой

$grep line ./*

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

Шаблоны могут включать в себя регулярные выражения. Причем список таковых для команды grep существенно шире, чем для команд манипулирования файлами. Так, кроме маски любой последовательности символов (*), любого одиночного символа (?), списка и диапазона символов ([a...z] и [a-z]), могут встречаться:

  • . (точка) — маска любого одиночного (но, в отличие от маски ?, обязательно присутствующего) символа; то есть при задании шаблона lin. будут найдены строки, содержашие line или lins, но не lin;
  • ^ и $ — маски начала и конца строки, соответственно: по шаблону ^line, будут найдены строки, начинающиеся с соответствующего слова, по шаблону же line$ — им заканчивающиеся;
  • маски повторения предыдущего шаблона, \{n\} — ровно n раз, \{n,\} — не менее n раз, \{,m\} — не более m раз, \{n,m\} — не менее n раз и не более m раз.

Маски повторения относятся к так называемым расширенным регулярным выражениям. Для их использования команда grep должна быть дана с опцией -e или в форме egrep — последняя часто определяется в общесистемном или пользовательском профильном файле как псевдоним команды grep:

alias grep='egrep -s'

или

alias grep egrep

в оболочках семейств shell и csh, соответственно.

При этом становятся доступными и другие возможности поиска — например, нескольких текстовых фрагментов (соедниненных логическим оператором «ИЛИ») одновременно. Делается это двояко. Первый способ — просто перечисление искомых фрагментов, каждый из которых предваряется опцией -e (и при необходимости экранируется кавычками):

$ grep -e pattern1 -e pattern2 files

При втором способе оператор между искомыми шаблонами задается в явном виде:

$ egrep 'pattern1|pattern2' files

Таким способом очень легко, например, составить оглавление для длинного текста (при наличии некоторой системы рубрикации в нем, разумеется). Для этого достаточно дать команду вроде следующей:

$ egrep 'Часть|Глава|Раздел|Параграф' filename

Для текста, включающего html- или TeX-разметку, роль рубрик могут выполнять соответствующие ее элементы, например:

$ egrep ' <h1>|<h2>|<h3>|<h4>' filename

Вывод команды grep может быть перенаправлен в файл, а при необходимости и предварительно отсортирован с помощью соответствующих командных конструкций перенаправления и конвейеризации.

Разумеется, тем же способом можно создать общее оглавление для серии фрагментов, записанных в виде самостоятельных файлов — для этого достаточно только перечислить их имена в качестве аргументов команды. Так, например, если есть необходимость составления детальной карты сайта, включающей ссылки на подрубрики внутри отдельных html-документов, следует применить конструкцию типа:

$ egrep '<h1>|<h2>|<h3>|<h4>' path/*.html > sitemap.html

Еще одно замечательное свойство команды grepegrep) — возможность получения шаблона не со стандартного ввода (то есть не путем набора его с клавиатуры), а из файла. Так, если для приведенного выше случая создать простой текстовый файл shablon, содержащий строку

Часть|Глава|Раздел|Параграф

выполнять операцию по сборке оглавления впредь (и в любом тексте, хоть частично совпадающем по структуре с рассмотренным) можно будет выполнять таким образом:

$ egrep -f shablon filename

Опция -f и указывает команде, что список параметров должен извлекаться из файла, указанного в качестве значения опции.

Список опций команды grep не исчерпывается указанными выше. Так, опция -i предписывает игнорировать различие регистров внутри искомого выражения, опция -h — подавляет вывод имен файлов (выводится только содержание найденных строк), тогда как опция -l, напротив, выводит только имена файлов, содержащих найденный шаблон, но не текстовый фрагмент, опция -n выводит номера найденных строк в соответствующих файлах. Весьма важной представляется опция -v, обеспечивающая инверсию поиска: при указании ее выводятся строки, не содержащие шаблона поиска.

Команда grep имеет и аналог для поиска в сжатых файлах — команду zgrep. Использование ее в целом аналогично, и останавливаться на нем я не буду.

Sed: средство потокового редактирования

Весьма часто при обработке текстов встает такая задача: заменить одно слово (или последовательность слов) на другое сразу во многих файлах. Как она решается «подоконными» средствами? Обычно — открытием всех подлежащих изменению документов в word-процессоре и применением функции поиска/замены последовательно в каждом из них.

Таким же способом можно воспользоваться и в POSIX-мире. Это просто, но уж больно скучно. Тем паче, что здесь есть очень эффективная альтернатива — средства потокового (неинтерактивного ) редактирования, примером которых является sed.

Потоковое, или неинтерактивное, редакторование не требует загруки документа в память (то есть открытия), как в обычных текстовых редакторах и word-процессорах. Нет, при нем подлежащий изменению файл (или группа файлов) обрабатываются построчно с помощью соответствующих команд, задаваемых как опции единой командной директивы. В наши дни это выглядит анахронизмом, однако в ряде случаев оказывается чрезвычайно эффективным. Каких? — ответ легко дать на нескольких конкретных примерах.

Так, при установке некоторых дистрибутивов Linux из числа Source Based вполне возможна ситуация, когда в какой-то момент времени в распоряжении пользователя не окажется никакого обычного текстового редактора. А необходимость внесения мелких изменений в конфигурационные файлы (например, в файл /etc/fstab) — возникнет. Так что делать — бежать срочно устанавливать свой любимый vim или emacs? И то, и другое — дело отнюдь не пяти минут. И тут самое время вспомнить про sed, который обязательно будет присутствовать в системе (поскольку он используется во многих установочных сценариях базовых пакетов).

Другой случай — во многих десятках, а то и сотнях, файлов требуется изменить одну-единственную строку, причем — одинаковым образом (например, заменить копирайт Васи Пупкина на Петю Лавочкина). Неужто для этой цели нужно вызывать мощный текстовый редактор, грузить в него немерянное количество документов, перемещаться тем или иным способом перемещаться к нужному фрагменту, вносить требуемое изменение? Отнюдь, ибо sed поможет и здесь, позволив выполнить изменение любого количества файлов в один в пакетном режиме.

Во всем блексе sed показывает себя при редактировании очень больших файлов (одно пролистывание которых требует немалого времени). А также — при редактировании сложных символьных последовательностей в нескольких файлах. Однажды, после очередной реконструкции моего сайта, передо мной встала задача тотальной модификации всех внутренних ссылок. Долго я с ужасом размышлял, как буду делать это в текстовом редакторе, и сколько ошибок при этом насажаю. Пока, раскинув мозгами, не нашел, как сделать это с помощью sed — быстро и, главное, безошибочно.

В самом общем виде sed требует двух аргументов — указания встроенной его команды и имени файла, к которому она должны быть применена. Впрочем, в качестве аргумента можно задать только простую команду, мало-мальски сложное действие (а команды поиска/замены принадлежат к числу сложных) необходимо определить через значения опции -e, заключенные в кавычки (одинарные или двойные — по ситуации). Что же касается имен файлов — то их в качестве аргументов можно указать сколько угодно, в том числе и с помощью масок типа *, *.txt и так далее. Правда, sed не обрабатывает содержимое вложенных подкаталогов, но это — дело поправимое (как — скоро увидим). Так что поиск и замена слова или их последовательности выполняются такой конструкцией:

$ sed -e 's/Вася Пупкин/Петя Лавочкин/' *

Здесь s — это команда поиска, Вася Пупкин — искомый текст, а Петя Лавочкин — текст для замены. В приведенной форме команда выполнит поиск и замену только первого вхождения искомого текста. Чтобы заменить текст по всему файлу, после последнего слэша (он обязателен в любом случае, без него sed не распознает конца заменяющего фрагмента) нужно указать флаг g (от global). Важно помнить, что если оставить заменяющее поле пустым, искомый текст будет просто удален.

По умолчанию sed выводит результаты своей работы на стандартный вывод, не внося изменений в файлы аргументы. Так где же здесь редактирование? Оно обеспечивается другой опцией — -i, указание которой внесет изменения непосредственно в обрабатываемый файл. В результате команда для замены, например, всех вхождений html на shtml во всех файлах текущего каталога будет выглядеть так:

$ sed -i -e 's/html/shtml' *

А что делать, если таким же образом нужно обработать файлы во всех вложенных подкаталогах? Придется вспомнить об универсальной команде find, описанной ранее. В форме

$ find . -name * -exec sed -i -e 's/html/shtml' * {}

она с успехом справится с этой задачей.

Я привел лишь элементарные примеры использования sed. На самом деле возможности его много шире — за деталями, как обычно, следует обратиться к документации.