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

Наш баннер
Вы можете установить наш баннер на своем сайте или блоге, скопировав этот код:
RSS новости