Инструменты для перевода и управления текстом в UNIX (исходники)

Крис Херборт

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

Введение

На заре истории UNIX® люди, работавшие с этой новой операционной системой, быстро нашли нишу, которую смогли заполнить; людям в университетах требовалась подходящая среда обработки текстов. Из-за скорости обработки данных и количества памяти в компьютерах в те дни программы должны были быть небольшими и относительно простыми. Это привело к знаменитой философии проектирования UNIX: "набор средств, работающих совместно над одним заданием." При помощи комбинирования нескольких небольших, но мощных инструментов обработки текста в каналы UNIX pipes, текст может быть преобразован и обработан несметным числом способов.

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

Прежде, чем начать

Если вы хотите следовать примерам из этой статьи и экспериментировать с ними, убедитесь, что вы получили доступ к среде командной строки UNIX. Это может быть осуществлено на вашей локальной машине через эмулятор терминала (часто называемый Terminal на современных рабочих столах; если вы слишком привыкли использовать Windows®, хорошо подойдет Cygwin), или на удаленной системе через SSH.

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

Как добиться, чтобы текст развернулся

Прежде чем начать управлять текстом при помощи нескольких текстовых утилит из всего многообразия UNIX, вам потребуется узнать, как добраться до какого бы то ни было текста. И прежде чем это сделать, вам потребуется освоить потоки стандартного ввода/вывода UNIX.

Стандартная библиотека C (и потому каждая программа UNIX) определила три стандартных потока: ввод, вывод, и сообщение об ошибке. Их иногда называют stdin, stdout и stderr вслед за глобальными переменными, которыми они представлены в каждой программе на C.

Когда вы перенаправляете вывод программы в файл при помощи оператора > в оболочке, вы посылаете поток ее стандартного вывода (stdout) в файл. Например: ls > this-dir посылает вывод ls в файл с именем this-dir.

Когда вы перенаправляете ввод программы из файла при помощи оператора < в оболочке, вы извлекаете содержимое файла в поток ее стандартного ввода (stdin). Например: sort < this-dir считывает содержимое файла с именем this-dir и предоставляет его как ввод команде sort.

Другой общий оператор для перенаправления стандартных потоков - это оператор / (pipe), который связывает поток стандартного вывода программы слева с потоком стандартного ввода программы справа. Например: ls / sort делает то же, что и предыдущие два примера, не требуя временного файла; вывод ls проходит напрямую через команду sort.

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

Перенаправляйте поток стандартных сообщений об ошибке в файл при помощи оператора 2>. Чаще всего вы это будете видеть, работая с командами, выводящими содержательные сообщения об ошибке, такими как инструмент make, используемый для сборки программ под UNIX: make 2> build-errors.

Эта команда запускает make и посылает любые сообщения об ошибках в файл build-errors. Подобным образом вы бы использовали 2/, чтобы перенаправить stderr в другую программу.

Если вас интересуют неприукрашенные детали, у других потоков также есть номера, хотя они почти никогда не используются (0 - это стандартный ввод, а 1 - стандартный вывод), за исключением одного на удивление обыкновенным оператором. В примере, приведенном в Листинге 1, оператор 2>&1 привязывает поток стандартных сообщений об ошибке к потоку стандартного вывода. В комбинации с оператором > вы получите stderr и stdout в одном и том же файле.

Листинг 1. Как привязать поток стандартных сообщений об ошибке к потоку стандартного вывода

                
make > build-output 2>&1

Наконец, команды

Есть две стандартные команды UNIX, которые часто используют для порождения некоторого текстового вывода: cat и echo.

Команда cat считывает каждый из файлов, указанных в ее аргументах, и выводит содержимое файлов в stdout. Команда echo выводит в stdout свои аргументы. Вы будете часто их видеть в качестве части более сложной командной pipe-строки (см. Листинг 2.

Листинг 2. Как использовать cat и echo

                
cat file1 file2 ... filen
echo arguments...

Но что, если вы хотите получить только первую часть файла, или последнюю? Есть два варианта cat под названиями head и tail (см. Листинг 3), которые сделают то, что вы хотите, выводя первые или последние десять строк, соответственно (для обеих команд вы можете указать другое количество строк при помощи опции-n).

Листинг 3. Как использовать head и tail

                
head file1 file2 ... filen
tail file1 file2 ... filen

У команды tail есть другая удобная опция -f (англ. follow, сопровождать). Она сообщает tail печатать последние десять строк указанного файла, но, вместо того, чтобы завершаться, она ждет, что в файле появится больше текста, и печатает его, как только он появляется. Это можно использовать, чтобы сопровождать вывод в журнал ошибок, к примеру, чтобы увидеть, какие ошибки появляются в момент, когда они записываются в журнал.

Как переводить текст

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

Команда tr позволяет вам переводить символы из одного набора в соответствующие символы из другого. Рассмотрим несколько примеров (Листинг 4), чтобы увидеть, как это работает.

Листинг 4. Как использовать tr, чтобы переводить символы

                
echo "a test" / tr t p
echo "a test" / tr aest 1234
echo "a test" / tr -d t
echo "a test" / tr '[:lower:]' '[:upper:]'

Взглянув на вывод этих команд (см. Листинг 5), вы получите ключ к тому, как работает tr (подсказка: это прямая замена символов из первого набора соответствующими символами из второго).

Листинг 5. Что сделала tr?

                
chrish@dhcp3 [199]$ echo "a test" / tr t p
a pesp

chrish@dhcp3 [200]$ echo "a test" / tr aest 1234
1 4234

chrish@dhcp3 [201]$ echo "a test" / tr -d t
a es

chrish@dhcp3 [202]$ echo "a test" / tr '[:lower:]' '[:upper:]'
A TEST

Первый и второй примеры достаточно просты: один символ заменяется другим. Третий пример, с опцией -d (англ. delete, удаление), полностью удаляет указанные символы из вывода. Это часто используется для удаления символов возврата каретки из текстовых файлов DOS, чтобы превратить их в текстовые файлы UNIX (см. Листинг 6). Наконец, в последнем примере применяются классы символов (эти имена в [: :]), чтобы конвертировать все буквы в нижнем регистре в буквы в верхнем регистре. Классы символов стандарта интерфейса переносимой операционной системы Portable Operating System Interface (стандарта POSIX) включают в себя:

  • alnum: алфавитно-цифровые символы (alphanumeric characters)
  • alpha: алфавитные символы (alphabetic characters)
  • cntrl: контрольные (непечатные) символы (control (non-printing) characters)
  • digit: цифровые символы (numeric characters)
  • graph: графические символы (graphic characters)
  • lower: алфавитные символы в нижнем регистре (lower-case alphabetic characters)
  • print: печатные символы (printable characters)
  • punct: символы пунктуации (punctuation characters)
  • space: символы пробела (whitespace characters)
  • upper: символы в верхнем регистре (upper-case characters)
  • xdigit: шестнадцатиричные символы (hexadecimal characters)

Листинг 6. Преобразование текстовых файлов DOS в текстовые файлы UNIX

                
tr -d '\r' < input_dos_file.txt > output_unix_file.txt

Хотя команда tr принимает команды среды локали C (посмотрите man locale, чтобы получить о них больше информации), не ожидайте от нее, что она сделает что-либо ощутимое с документами в кодировке UTF-8, - например, сможет заменить символы с диакритиками в нижнем регистре соответствующими символами в верхнем регистре. Команда tr лучше всего работает с ASCII и другими стандартными локалями C.

Сложный поиск и замена при помощи sed

Возможности односимвольной замены (или удаления), предоставляемые командой tr, велики в особых случаях, но не очень гибки. Что, если вам требуется заменить одно слово другим, или ряд пробелов и символов табуляции одним пробелом?

К счастью, в вашем распоряжении команда sed (англ. Stream EDitor, редактор потока), которая обеспечивает мощный поиск совпадений и замену при помощи регулярных выражений . Регулярные выражения - это сложные указания шаблонов, образуемые при помощи строительных блоков, которые в итоге выглядят более всего похожими на помехи в модеме по мере того, как шаблон усложняется. Детальное руководство по регулярным выражениям - это материал для другой статьи, а здесь вы кратко ознакомитесь с некоторыми удобными для использования в sed шаблонами.

Основной формат команды sed показан в Листинге 7. Шаблон (pattern) - это регулярное выражение, используемое для поиска совпадений во входном потоке (обычно либо перенаправленном из другой программы, либо из текстового файла), а замена (replacement) - это текст, который следует вставить вместо текста, совпавшего с шаблоном. Флаги (flags) - это одиночные символы, контролирующие поведение этой подстановки. Самый часто используемый флаг - это g (применить замену ко всем непересекающимся экземплярам, которые совпадают с шаблоном, вместо того, чтобы применить ее только к первому совпадению).

Шаблон и замена могут быть практически чем угодно, и они не должны быть в одно-однозначном соответствии, как это происходит с командой tr.

Listing 7. Команда sed

                 
sed -e s/pattern/replacement/flags

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

Листинг 8. Самое простое регулярное выражение

                 
chrish@dhcp3 [334]$ echo "Replace one word" / sed -e s/one/another/
Replace another word

Вы можете поставить один или несколько символов в квадратные скобки, чтобы создать множество; любой символ из этого множества совпадет. Заменим все гласные на символы подчеркивания (см. Листинг 9).

Листинг 9. Поиск совпадений элемента множества

                 
chrish@dhcp3 [338]$ echo "This is a test" / sed -e s/[aeiouy]/_/g
Th_s _s _ t_st

Обратите внимание на использование флага g, чтобы применить шаблон/замену к каждому совпадению вместо просто первого.

Команда sed также учитывает именованные классы символов, которые поддерживает команда tr; они определены в POSIX, но здесь применяется другой синтаксис. Листинг 10 показывает вам, как заменять любой пробел (знаки табуляции, пробелы и т.д.):

Листинг 10. Поиск совпадений элемента именованного класса символов

                 
chrish@dhcp3 [345]$ echo -e 'hello\tthere'   
hello   there
chrish@dhcp3 [346]$ echo -e 'hello\tthere' / sed -e 's/[[:space:]]/, /'
hello, there

Флаг -e команды echo указывает ей расширять escaped-символы стиля C; в данном случае он превратит \t в знак табуляции для вас.

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

Листинг 11. Это, вероятно, не то, что вы хотели

                 
chrish@dhcp3 [339]$ echo "This is a test" / sed -e s/./_/g
______________

Теперь, когда самое основное вы уже увидели, рассмотрим несколько дополнительных модификаторов шаблонов; теперь вы станете также использовать опцию -E вместо -e, чтобы пользоваться усовершенствованными регулярными выражениями. Символ ? обозначает поиск нуля или одного соответствия предшествующему элементу шаблона; символ * означает поиск нуля или более соответствий предшествующему ему элементу. Символ + обозначает поиск одного или нескольких соответствий предшествующему элементу. Символ ^ соответствует началу строки, а $ - концу. Это можно увидеть в действии, как показано в Листинге 12.

Листинг 12. Множественные соответствия в действии

                 
chrish@dhcp3 [356]$ echo "hellooooo" / sed -E 's/o?$/_/g'
helloooo_
chrish@dhcp3 [357]$ echo "hellooooo" / sed -E 's/o*$/_/g'
hell_
chrish@dhcp3 [358]$ echo "hellooooo" / sed -E 's/o+$/_/g'
hell_

Если вы поставите элементы шаблона в круглые скобки, то сможете использовать найденные совпадения в строке замены. Они называются группами; благодаря ним поиск регулярных выражений обретает высокую эффективность, но читаемость выражения в существенной степени затрудняется. Например, в Листинге 13 ищется один или несколько символов l ( эль ), за которыми следуют ноль или несколько символов o. Они заменяются содержимым второй группы, за которым следует первая, то есть, на самом деле, они меняются местами. Обратите внимание, что на группы следует указывать при помощи бэкслеша с номером группы в шаблоне.

Listing 13. Match groups

                 
chrish@dhcp3 [361]$ echo "hellooooo" / sed -E 's/(l+)(o*)$/\2\1/g'
heoooooll

Можно также осуществлять поиск соответствий специальному количеству повторений шаблона, указывая это количество в фигурных скобках. Например, шаблону o{2} будут соответствовать два (и только два) символа o.

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

Putting it together

Теперь, когда вы столкнулись с несколькими очень простыми регулярными выражениями, мы можем перейти к более полезным. Работая с выводом ls -l ( длинный список файлов), вы можете извлечь информацию о правах доступа, размере и названии. В Листинге 14 приведен пример вывода ls -l для вашей дальнейшей работы.

Листинг 14. Типичный вывод ls -l

                 
chrish@dhcp3 [365]$ ls -l / tail
drwx------   3 chrish    wheel   102 Jun 14 21:38 gsrvdir501
drwxr-xr-x   2 chrish    wheel    68 Jun 16 16:01 hsperfdata_chrish
drwxr-xr-x   3 root      wheel   102 Jun 14 23:38 hsperfdata_root
-rw-r--r--   1 root      wheel   531 Jun 14 10:17
 illustrator_activation.plist
-rw-r--r--   1 root      wheel   531 Jun 14 10:10 indesign_activation.plist
-rw-------   1 nobody    wheel    24 Jun 16 16:01 objc_sharing_ppc_4294967294
-rw-------   1 chrish    wheel   132 Jun 16 23:50 objc_sharing_ppc_501
-rw-------   1 security  wheel    24 Jun 16 10:04 objc_sharing_ppc_92
-rw-r--r--   1 root      wheel   531 Jun 14 10:05 photoshop_activation.plist
-rw-r--r--   1 root      wheel   928 Jun 14 10:17 serialinfo.plist

Как вы можете заметить, здесь есть семь колонок:

  • Права доступа (Permissions)
  • Количество связей (Number of links)
  • Владелец (Owner)
  • Группа (Group)
  • Размер (Size)
  • Время последнего изменения (Last modification time)
  • Имя (Name)

Создадим несколько регулярных выражений для выделения содержимого каждой из них:

  • .([r-][w-][x-]){3} -- права доступа (Используйте . для обнаружения первого символа, потому что он может быть любым из нескольких различных специальных символов.)
  • [[:digit:]]+ -- количество связей
  • [A-Za-z0-9_\-\.]+ - -- владелец (Вы также можете это использовать для выделения группы.)
  • [[:digit:]]+ -- размер
  • .{3} [0-9 ]{2} [0-9 ][0-9]:[0-9][0-9] -- время изменения (Это можно было бы несколько упростить, так как все файлы менялись в июне, и также можно было бы это уточнить, указав имена месяцев.)
  • .+$ - имя (После всего остального будут выделены все символы вплоть до конца строки.)

В промежутках вам придется объединить все эти шаблоны записью [[:space:]]+, так как вы не знаете, разделены ли колонки пробелами, знаками табуляции, или же их комбинациями. Также вы захотите поместить права доступа, размер и имя в группы, чтобы иметь возможность использовать их в строке замены. Как видно из Листинга 15, регулярные выражения быстро становится трудно читать.

Листинг 15. Итоговое регулярное выражение. Прикройте глаза!

                 
(.([r-][w-][x-]){3})[[:space:]]+[[:digit:]]+[[:space:]]+([A-Za-z0-9_\-\.]
+[[:space:]]+){2}([[:digit:]]+)[[:space:]]+.{3} [0-9 ]{2} [0-9
 ][0-9]:[0-9][0-9][[:space:]]+(.+)$

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

  1. Весь блок прав доступа
  2. Последняя выделенная группа rwx в блоке прав доступа
  3. Группа (последний обнаруженный элемент в части шаблона, отвечающей за владельца/группу)
  4. Размер
  5. Имя

В Листинге 16, вывод ls -l заменяется так, что в итоге показывает имя файла, права доступа и размер.

Листинг 16. Перекомпонованный вывод

                 
chrish@dhcp3 [382]$ ls -l / tail / sed -E
 's/(.([r-][w-][x-]){3})[[:space:]]+[[:digit:]]+[[:space:]]+([A-Za-z0-9_\-\.
 ]+[[:space:]]+){2}([[:digit:]]+)[[:space:]]+.{3} [0-9 ]{2} [0-9
 ][0-9]:[0-9][0-9][[:space:]]+(.+)$/\5 (\1) has \4 bytes of data/'
gsrvdir501 (drwx------) has 102 bytes of data
hsperfdata_chrish (drwxr-xr-x) has 68 bytes of data
hsperfdata_root (drwxr-xr-x) has 102 bytes of data
illustrator_activation.plist (-rw-r--r--) has 531 bytes of data
indesign_activation.plist (-rw-r--r--) has 531 bytes of data
objc_sharing_ppc_4294967294 (-rw-------) has 24 bytes of data
objc_sharing_ppc_501 (-rw-------) has 132 bytes of data
objc_sharing_ppc_92 (-rw-------) has 24 bytes of data
photoshop_activation.plist (-rw-r--r--) has 531 bytes of data
serialinfo.plist (-rw-r--r--) has 928 bytes of data

Победа! Вы полностью изменили вывод.

Как это сделать при помощи Perl

Язык программирования и написания скриптов Perl часто применяется как в высшей степени мощная замена командам tr и sed, на которые вы только что смотрели. Короткая программа на Perl, часто вводимая непосредственно из командной строки, может иногда сделать больше, чем эквивалентная строка команды tr или sed.

Опция Perl -p указывает ему обрабатывать каждую строку из стандартного ввода и печатать результаты на стандартный вывод. Опция -e позволяет указать выражение на Perl (на самом деле, программу) в командной строке.

В Листинге 17 показано, как дублировать примеры из Листинга 5 при помощи Perl.

Листинг 17. Как использовать Perl для решения задач команды tr

                 
chrish@dhcp3 [248]$ echo a test / perl -p -e 'tr/t/p/;'
a pesp

chrish@dhcp3 [249]$ echo a test / perl -p -e 'tr/aest/1234/;'
1 4234

chrish@dhcp3 [250]$ echo a test / perl -p -e 'tr/t//d;'
a es

chrish@dhcp3 [251]$ echo a test / perl -p -e 'tr/a-z/A-Z/;'
A TEST

Выражение Perl tr обладает слегка другим синтаксисом, более похожим на выражения поиска и замены sed. Также обратите внимание, что в последнем примере символы верхнего и нижнего регистров указаны при помощи диапазонов.

Поддержка регулярных выражений в Perl превосходна, и все приведенные выше примеры использования sed будут работать как верные выражения Perl. В Листинге 18 показан пример с ls -l из Листинга 16 на Perl; нигде, кроме синтаксиса строки команды Perl, не потребовалось никаких изменений.

Листинг 18. Перестройка вывода ls при помощи Perl

                 
chrish@dhcp3 [384]$ ls -l / tail / perl -p -e
 's/(.([r-][w-][x-]){3})[[:space:]]+[[:digit:]]+[[:space:]]+([A-Za-z0-9_\-\.]
+[[:space:]]+){2}([[:digit:]]+)[[:space:]]+.{3} [0-9 ]{2} [0-9
 ][0-9]:[0-9][0-9][[:space:]]+(.+)$/\5 (\1) has \4 bytes of data/'
gsrvdir501 (drwx------) has 102 bytes of data
hsperfdata_chrish (drwxr-xr-x) has 68 bytes of data
hsperfdata_root (drwxr-xr-x) has 102 bytes of data
illustrator_activation.plist (-rw-r--r--) has 531 bytes of data
indesign_activation.plist (-rw-r--r--) has 531 bytes of data
objc_sharing_ppc_4294967294 (-rw-------) has 24 bytes of data
objc_sharing_ppc_501 (-rw-------) has 132 bytes of data
objc_sharing_ppc_92 (-rw-------) has 24 bytes of data
photoshop_activation.plist (-rw-r--r--) has 531 bytes of data
serialinfo.plist (-rw-r--r--) has 928 bytes of data

Замечательно в этом то, что вы можете оттачивать ваши регулярные выражения как при помощи sed, так и Perl, и все равно использовать их в системах, в которых доступен либо только один из них, либо только другой. А в Perl в вашем распоряжении полный набор программных конструкций, которыми вы можете воспользоваться, осуществляя даже более сложную обработку текста.

Резюме

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

 

Об авторе

 
 

Крис Херборт (Chris Herborth) уже более 10 лет пишет об операционных системах и программировании. Он выигрывал награды как старший технический писатель. Если он не играет с сыном Алексом или просто проводит время с женой, Крис посвящает свое свободное время написанию статей и исследованию видео игр (то есть, игре).


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=6112