Перевод 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аслаждайтесь!