Next Previous Contents

Unix Review Column 12 -- Внутpенние документы (Here documents)

Randal Schwartz

Январь 1997

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


Next Previous Contents