Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

Упражнение 9-12

Измените match так, чтобы в ней использовалась версия matchstar, в которой ищется крайне левое максимальное соответствие. Кроме того, следует добиться возвращения позиции символов начала и конца текста, соответствующего шаблону. Теперь создайте новую версию grep, кото­рая вела бы себя как старая, но выполняла замену текста, соответству­ющего шаблону, заданным новым текстом и выводила полученные стро­ки. Пример вызова:

% grep 'homoiousian' 'homoousian' mission.stmt

Упражнение 9-13

Измените match и grep так, чтобы они работали со строками симво­лов Unicode формата UTF-8. Поскольку UTF-8 и Unicode являются рас­ширением набора 7-битового ASCII, такое изменение будет совместимо с предыдущей версией. Регулярные выражения, так же как и текст, в ко­тором происходит поиск, должны корректно работать с UTF-8. Как в этом случае должны быть реализованы классы символов?

Упражнение 9-14

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

9.3. Программируемые инструменты

Множество инструментов группируется вокруг языков специального назначения. Программа grep — всего лишь одна из целого семейства ин­струментов, которые используют регулярные выражения или другие языки для разрешения программистских проблем.

Одним из первых подобных инструментов стал командный интерпре­татор, или язык управления заданиями. Достаточно быстро стало понят­но, что последовательность команд можно поместить в отдельный файл, а потом запустить экземпляр интерпретатора команд, или оболочки (shell), указав этот файл в качестве источника ввода. Следующим шагом стало уже добавление параметров, условных выражений, циклов, переменных и т. п., то есть всего того, из чего в нашем представлении состоит нормальный язык программирования. Единственное отличие заключа­лось в том, что существовал только один тип данных — строки, а операто­рами в программах-оболочках являлись различные программы, осуще­ствлявшие порой достаточно интересные вычисления. Несмотря на то что программирование под конкретные оболочки сейчас уже уходит в про­шлое, уступая место работе с инструментами типа Perl в командных сре­дах или щелканью по кнопкам в графических средах, этот "старинный" подход по-прежнему остается простым и достаточно эффективным спо­собом создания каких-то сложных операций из простых частей.

Еще один программируемый инструмент, Awk, представляет собой небольшой специализированный язык, который предназначен для отбо­ра и преобразования элементов входного потока; он ищет в этом потоке соответствия заданным шаблонам, а когда находит, то выполняет свя­занные с шаблонами действия. Как мы уже видели в главе 3, Awk авто­матически читает входные файлы и разделяет каждую прочитанную строку на поля, обозначаемые от $1 до $NF, где NF есть количество полей в строке. Его поведение "по умолчанию" удобно для выполнения боль­шого числа типовых действий, поэтому многие полезные программы пи­шутся на Awk в одну строчку. Так, например, программа

# split.awk: расщепляет текст на отдельные слова

{ for (i = 1; i <= NF; i++) print $i }

выводит "слова" по одному в строке. И напротив, вот программа f mt, ко­торая заполняет каждую выводимую строку словами (до 60 символов); пустая строка означает конец параграфа:

# fmt.awk: форматирует в 60-символьные строки

/. / { for (i = 1; i <= NF; i++) addword($i) } # непустая строка

/ ^ $/ { printlineO; print "" } # пустая строка

END { printlineO }

function addword(w) {

if (length(line) + 1 + length(w) > 60)

printline()

if (length(line) == 0)

line = w

else

line = line " " w

}

function printline() {

if (length(line) > 0) {

print line

line = “”

}

}

Мы часто используем fmt для того, чтобы заново разбить на абзацы поч­товые сообщения и другие короткие документы; ее же мы использовали в главе 3 для форматирования вывода программы markov.

Программируемые инструменты часто берут свое начало от малых языков программирования, созданных для более простого выражения решений проблем в какой-то узкой предметной области. Прелестным примером является инструмент Unix под названием eqn, который обра­батывает математические формулы. Язык, применяемый в нем для вво­да, очень похож на обычный: например, выражение П/2 мы прочитали бы вслух как "пи на два" (pi over two), и в этом языке оно так и записывает­ся — pi over 2. Тот же подход применяется и в ТЕХ, в нем это выраже­ние было бы записано как \pi \over 2. Если для проблемы, которую вы пытаетесь разрешить, уже существует естественный или привычный способ записи, используйте его или, на худой конец, попробуйте его адаптировать: не пытайтесь писать с нуля.

Awk развился из программы, которая использовала регулярные выраже­ния для выявления аномалий в записях телефонного трафика; теперь же Awk содержит в себе переменные, выражения, циклы и т. п., — и это делает его полноценным языком программирования. Perl и Tcl первоначально проектировались с целью совместить удобство и выразительность малых языков с мощью полноценных языков программирования. В результате по­лучились действительно удобные языки программирования общего назна­чения, хотя, конечно, чаще всего их используют для обработки текста.

Для подобных инструментов применяют общий термин — языки скрип­тов (scripting languages). Такое название объясняется их происхождением от ранних командных интерпретаторов, вся "программируемость" ко­торых ограничивалась исполнением заранее записанных "сценариев" (script) программ. Языки скриптов позволяют использовать регуляр­ные выражения более творчески: не только для поиска соответствий шаб­лону — простого обнаружения соответствия, но и для определения участ­ков текста, которые должны быть изменены. Именно это осуществляется в двух командах regsub (от regular expression substitution — замена с помо­щью регулярных выражений), реализованных в приводимой ниже про­грамме на языке Tcl. Программа эта является несколько более общей фор­мой программы из главы 4, получающей биржевые котировки; новая версия выполняет это, получая данные из URL, передаваемого ей в каче­стве первого аргумента. Первая замена удаляет строку http://, если она присутствует; вторая — удаляет символ / и заменяет его пробелом, разби­вая, таким образом, аргумент на два поля. Команда lindex получает поля из строки (начиная с индекса 0). Текст, заключенный в квадратные скоб­ки, выполняется как команда Tcl и заменяется результирующим текстом; последовательность $х заменяется значением переменной х.

# geturl.tcl: получает документ из URL

# ввод имеет вид [http://]abc.def.com[/whatever... ]

# если присутствует, удалить http://

regsub "http://" $argv "" argv ;

# в начале строки заменить символ / пробелом

regsub "/" $argv " " argv ;

# выполнить сетевое соединение

set so [socket [lindex $argv 0] 80] ;

set q "/[lindex $argv 1]"

puts $so "GET $q HTTP/1.0\n\n" ; # послать запрос

flush $so

# пропустить заголовок

while {[gets $so line] >= 0 && $line != ""} {} ;

# прочесть и вывести весь ответ

puts [read $so] ;

Этот скрипт, как правило, производит весьма объемистый вывод, большую часть которого составляют тэги HTML, заключенные между < и >. Perl удобен для текстовых подстановок, так что нашим следующим инструментом станет скрипт на Perl, который использует регулярные выражения и подстановки для удаления тэгов:

# unhtml.pl: delete HTML tags

while (<>) { # собирает весь ввод в одну строку

$str .= $_; # накапливая вводимые строки }

$str =~ s/<[^>]*>//g; # удалить <...>

$str =~ s/ / /g; # заменить   пробелом

$str =~ s/\s+/\n/g; # сжать свободное место

print $str;

Для тех, кто не знаком с Perl, этот код будет загадкой. Конструкция

$str =~ s/regexp/repl/g

в строке str подставляет строку repl вместо текста, соответствующего регулярному выражению гедехр (берется крайне левое максимальное соответствие); завершающий символ д (от "global") означает, что дей­ствия надо произвести глобально, для всех найденных соответствий, а не только для первого. Последовательность метасимволов \s является сокращенным обозначением символа пустого места (пробел, знак табу­ляции, символ перевода строки и т. п.); \n означает перевод строки. Строка " " — это символ HTML, как и те, о которых мы упоминали в главе 2, он означает non-breakable space — неразрывный пробел.

Собрав все написанное воедино, мы получим совершенно идиотский, но функционирующий web-браузер, реализованный как скрипт команд­ного интерпретатора, состоящий из одной строки:

# web: получает web-страницу и форматирует ее текст,

# игнорируя HTML

geturl.tcl $1 | unhtml.pl | fmt.awk

Такой вызов получит web-страницу, отбросит все управление и форма­тирование и отформатирует текст по своим собственным правилам. По­лучился быстрый способ достать страницу текста из web.

Отметьте, что мы неспроста использовали сразу несколько языков (Tel, Perl, Awk) и в каждом из них — регулярные выражения. Собствен­но говоря, мощь различных нотаций и состоит как раз в подборе наилуч­шего варианта для каждой проблемы. Tel особенно хорош для получе­ния текста из сети, Perl и Awk прекрасно редактируют и форматируют текст, а регулярные выражения — отличный способ определения фраг­ментов текста, которые должны быть найдены и изменены. Объедине­ние всех этих языков получается гораздо более мощным, чем любой из них в отдельности. Целесообразно разбить задачу на части, если можно выиграть за счет составления правильной нотации в каждой из них.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]