Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott lt;ott@phtd.tpu.edu.ru>
Одним из отточенных свойств Perl является возможность обеспечения данных внутри программы, представляя данные в различных формах, вместо того, чтобы хранить эти данные во временных файлах или в файлах настройки. Одним из часто используемых способов являются ``here document (внутренний документ)'' (также называемые ``here-doc''). Давайте посмотрим что это такое.
В первую очередь, ``внутренний документ'' это ничто иное, как длинное (обычно многостроковое) строковое значение, но объявленное особым образом. Вот простой пример:
$a = <<END; Some stuff goes here. END
Это аналог записи:
$a = "Some stuff\ngoes here.\n";
Hо заметьте, что мы расположили строку на двух отдельных строках. Ключем к такому поведению является последовательность <<END. Последовательность << говорит ``это строковое значение, которое начинается со следующей строки и продолжается до тех пор, пока мы не найдем указанный маркер''. В нашем случае, концом строки является строка ``END'', которая должна располагаться на отдельной строке. Все символы между началом строки и до маркера (включая последний перевод строки) считается частью значения.
Заметьте, что за выражением <<END следует точка с запятой. Это ничто иное, как маскированная строка, чтобы сделать синтаксис выражения корректным. Я всегда буду помещать точку с запятой в этом выражении.
Вот другой пример, показывающий как может быть произведено умножением ``встроенных документов'' в одной и той же исходной строке:
print <<HELLO, <<WORLD; hi HELLO there WORLD
Заметьте, что в самом деле это выражение аналогично строке:
print "hi\n","there\n";
хотя и красиво маскировано.
А что если мы не хотим иметь переводы строки? Хорошо, это достаточно
просто. Одним из простых способов является искажение результата с помощью
функции substr
:
$data = substr(<<THING,0,-1); This is a long quoted line. THING
и теперь $data
будет содержать строку, без завершающего символа
новой строки, благодаря функции substr
, выбирающей все символы
кроме последнего. Другим способом является использование регулярного
выражения, но тут я немного заскочу вперед:
$stuff = join(":", <<END =~ /(.+)/g); hi there this\tis\ta\ttest of the best! END
Здесь приведено много разных вещей, так что позвольте мне внести
ясность. Значение $stuff
получается из объединения элементов
списка через символ ``:''. Список получается при выполнении операции поиска
по регулярному выражению m//g
в списочном контексте, выполняемой
для всех соответствий выражению ``.+'' внутри строки. Эта строка получается
из ``внутреннего документа'', ограниченного словом END
. Результат
данного регулярного выражения является списком, который выглядит примерно
так:
"hi", "there", "this\tis\ta\test", "of the best!"
Который при соединении будет выглядеть так:
"hi:there:this\tis\ta\test:of the best!"
Вот вам задачка (не такая сложная, если вы знаете!)... заключается в
том, являются ли последовательности \t
символами табуляции или
просто последовательностью из символа обратный слэш, за которым следует
буква t
? Хорошо. по умолчанию ``внутренние документы'' работают
как строки, заключенные в двойные кавычки, в которых происходит подстановка
переменных, а символы подобные \t
получают свои значения.
Так что в этом случае строка будет содержать три символа табуляции. Что делать в том случае, если мы не хотим этого... мы просто хотим получить то, что мы видим, также как и в случае строк, заключенных в одинарные кавычки? Хорошо, мы можем задать это для ``внутреннего документа'' заключив конечный таг в одинарные кавычки:
$stuff = <<'END'; This\thas\tno\ttabs! END
что приведет к получению следующей строки:
$stuff = 'This\thas\tno\ttabs!' . "\n";
Заметьте, что мы все равно получаем перевод строки в конце документа. Я могу также явно указать использование интерполяции переменных:
$stuff = <<"END"; This\thas\ttabs\tnow! END
Похоже, что мне необходимо набирать больше текста. Hо мы должны использовать кавычки в тех случаях, когда разделитель содержит пробелы:
$stuff = <<"END OF DATA"; This is my home: $ENV{HOME} And this is my shell: $ENV{SHELL} END OF DATA
Заметьте, что я здесь также использовал переменные (значения переменных
среды $HOME
и $SHELL
). Очень важно соблюдать правильное
количество пробельных символов. Лишние пробелы в начале или в конце маркера
приведут к тому, что Perl пропустит эту строки и будет продолжать поиск
настоящей отметки.
Технология сканирования ``внутреннего документа'' с помощью регулярного выражения может быть расширена в будущем. Hапример, представьте, что нам необходим ассоциативный массив, загружаемый парами ключ-значение:
%data = <<END =~ /(\w+): (.*)/g; fred: Fred Flintstone barney: Barney Rubble betty: Betty Rubble wilma: Wilma Flintstone END for (sort keys %data) { print "$_ => $data{$_}\n"; } print <<QUOTE; This is to inform you that $data{"fred"} and $data{"wilma"} are married. QUOTE
В этом коде %data
заполняется парами элементов, которые
получаются из каждого соответствия регулярному выражению. Это регулярное
выражение соотвествует ключу (такому как fred или barney) и необходимому
значению (такому как ``Fred Flintstone''). Цикл foreach
в конце
кода выдает на печать полученный ассоциативный массив. А второй
``внутренний документ'' показывает нам возможность доступа к данному
ассоциативному массиву как части другого ``внутреннего документа''.
Что приводит нас к следующей идее: использование ``внутреннего документа'' как ``генератора форм писем''. Вот как это может выглядеть:
for (<<'EOF' =~ /(.+)/g) { fredf:Fred Flintstone:$25 barneyb:Barney Rubble:$100 bettyb:Betty Rubble:$0.05 EOF ($email, $person, $owe) = split /:/; print <<EOM; ## mail for $person mail -s "$person, you deadbeat!" $email <<INPUT Hey, $person, you owe us $owe! Pay up, or else! INPUT ## end of mail for $person EOM }
Прошу извинить за такие странные отступы, но ``внутренние документы''
имеют дополнительные пробелы в начале. В этом коде делается несколько
вещей, так что позвольте мне описать их. В начале , цикл foreach
проходит по списку полученному из просмотра всех строк во ``внутреннем
документе''. Внутри цикла foreach loop
, строка (в $_
)
разбивается по символу двоеточие, и в результате мы получаем три
переменных— $email
, $person
и $owe
.
Следующим шагом является выдача другого ``внутреннего документа'', ссылаясь на три переменных (иногда несколько раз). Этот документ является скриптом командного процессора, который вы наверное будете выполнять. Заметьте, что этот скрипт также содержит ``внутренний документ'' командного процессора (из которого Perl позаимствовал эту идею), обозначенный меткой ``INPUT''. Смотрите, сколько уровней связано здесь. Запустите эту программу и вы получите нечто похожее на следующий код:
... ## mail for Barney Rubble mail -s "Barney Rubble, you deadbeat!" barneyb <<INPUT Hey, Barney Rubble, you owe us $100! Pay up, or else! INPUT ## end of mail for Barney Rubble ...
Заметьте, что в последней программе имеются некрасивые отступы. Хорошо, сделав небольшую работу, мы сможем избежать их, используя хитрое регулярное выражение:
for (1..10) { @data = <<END =~ /\t\t(.*)\n/g; Data one $_ Data two $_ END print "$_: @data\n"; }
Заметьте, что регулярное выражение отбрасывает два символа табуляции, которые я использовал для создания отступов в этой части программы. Классно. Hо к сожалению, метка конца все равно должна иметь правильный отступ слева.
И в завершение, последний набор приемов. Внутренние документы могут быть заключены в обратные кавычки, а не только в двойные или одинарный кавычки. Это выполняется помещением метки конца в обратные кавычки:
$shell_out = <<`SHELL`; for i in * do echo -n \$i: sum \$i done SHELL print "shell said: $shell_out\n";
В этом примере, маркер конца ``SHELL'' помещен в обратные кавычки,
заставляя все строки до маркера считаться командами для командного
процессора. В этом примере командами является цикл командного процессора,
проходящим по всем именам в текущем каталоге, присваивая переменной
командного процессора $i
имя каждого из файлов. Для каждого файла
выдается имя, за которым следует контрольная сумма этого файла.
Заметьте, что мы замаскировали знак доллара с помощью символа обратный слэш. Это необходимо, поскольку во внутренних документах, заключенных в обратные кавычки производится подстановка переменных и специальных знаков, также как и в строках, заключенных в обратные кавычки. Однако выполнив небольшую работу мы можем получить документ, который не испытывает таких проблем:
$shell_in = <<'IN'; for i in * do echo -n $i: sum $i done IN $shell_out = `$shell_in`; print "shell said: $shell_out\n";
В этом примере, я сначала создаю строку из внутреннего документа
заключенного в одинарные кавычки (без подстановки переменных, так что
$i
останется просто $i
). А затем, я вставляю эту строку в
обыкновенную строку, заключенную в обратные кавычки, что приводит к
выполнению правильной команды командного процессора.
Как вы смогли увидеть, внутренние документы позволяют сделать легким
включение в вашу программу больших частей постоянного текста. Другим
способом выполнить это является использование файлового дескриптора
DATA
, но мы оставим это до будущих времен. Hаслаждайтесь!