Время от времени нам нужно создать строковую переменную, которая занимает несколько строк. Как и всегда в Perl, для этого есть несколько способов. Одно из типичных решений - использование here-документов.

Here-document позволяет создать строковую переменную, занимающую несколько строк и сохранить пробелы и переводы строк. Если запустить следующий код, он выведет в точности то, что мы видим от слова "Дорогой" и до строки перед вторым END_MESSAGE.

Неинетрполируемый here-документ

#!/usr/bin/perl
use strict;
use warnings;

my $name = 'Foo';

my $message = <<'END_MESSAGE';
Дорогой $name,

я собираюсь отправить тебе это сообщение.

с наилучшими пожеланиями
  Perl Maven
END_MESSAGE

print $message;

Вывод:

Дорогой $name,

я собираюсь отправить тебе это сообщение.

с наилучшими пожеланиями
  Perl Maven

Here-документ начинается с двух знаков "меньше" << и произвольной строки, которая будет указателем окончания here-документа, и точки с запятой ;, указывающей конец выражения. Что довольно странно, ведь выражение на этом не заканчивается. Содержание here-документа только как раз только начинается со следующей строки (в нашем случае со слова "Дорогой") и продолжается до того места, где perl найдет наш произвольный указатель окончания. В данном случае строку END_MESSAGE.

Если вы уже видели here-документы в коде, возможно, вас удивили одиночные кавычки вокруг первого END_MESSAGE. Скорее всего, если вы встречали примеры here-документов в интернете или в корпоративных сетях, там открывающая часть была без кавычек, вроде этого:

my $message = <<END_MESSAGE;
...
END_MESSAGE

Это сработает так же, как если бы вы поместили END_MESSAGE в двойные кавычки, как в следующем примере, но это устаревший синтаксис и он перестанет работать начиная с версии 5.20. Не делайте так! Не используйте here-документы без кавычек вокруг определения указателя окончания.

my $message = <<"END_MESSAGE";
...
END_MESSAGE

Если вам уже знакома разница между строками в одиночных и двойных кавычках в Perl, то, наверное, вы не удивитесь тому, что here-документы работают точно так же. Единственная разница в том, что кавычки ставятся вокрг указателя окончания, а не вокруг самой строки. Если кавычек нет, Perl по умолчанию понимает такую строку так, как если бы она была в двойных кавычках. If you already know the

Если вы вернетесь к первому примеру, вы заметите в нашем here-документе переменную $name, которая осталась в таком же виде в выводе. Это потому, что Perl не пытался заменить $name содержимым этой переменной. (Мы могли бы и не объявлять эту переменную в коде. Вы можете попробовать запустить этот скрипт, убрав строку с my $name = 'Foo';.)

Интерполяция в here-документе

В следующем примере мы используем двойные кавычки вокруг указателя окончания, и таким образом переменная $name будет интерполирована:

use strict;
use warnings;

my $name = 'Foo';
my $message = <<"END_MSG";
Привет $name,

Как дела?
END_MSG

print $message;

Результат выполнения этого скрипта:

Привет Foo,

как дела?

Внимание: точное повторение указателя окончания в конце

Обратите внимание. Необходимо убедиться, что указатель окончания должен быть скопирован в конце текста в точности как в начале. Никаких пробелов в начале или в конце. Иначе Perl не распознает его и будет думать, что это продолжение here-документа. Это в том числе значит, что нельзя ставить отступы перед указателем окончания, чтобы они соответствовали отступам в остальном коде. Или можно?

Here-документы и отступы

Если нам нужен here-документ в месте, где должны быть отступы перед кодом, это порождает две проблемы:

#!/usr/bin/perl
use strict;
use warnings;

my $name = 'Foo';
my $send = 1;

if ($send) {
    my $message = <<"END_MESSAGE";
        Дорогой $name,
    
        я собираюсь отправить тебе это сообщение.
    
        с наилучшими пожеланиями
          Perl Maven
END_MESSAGE
    print $message;
}

Первая состоит в том, что, как уже упоминалось, указатель окончания строки должен быть в точности одинаковым в начале и в конце документа, так что в конце перед ним не может быть отступов.

Вторая проблема в том, что в выводе мы увидим все эти пробелы перед каждой строкой:

        Дорогой Foo,
    
        я собираюсь отправить тебе это сообщение.
    
        с наилучшими пожеланиями
          Perl Maven

Недостаток отступов перед указателем окончания может быть решен, если мы сразу включим их при его объявлении в начале документа: (здесь я использую 4 пробела, так как табы плохо смотрятся в статье, но в коде их можно использовать, если вы принадлежите к лагерю сторонников отступов-с-помощью-табов)

    my $message = <<"    END_MESSAGE";
       ...
    END_MESSAGE

Лишние отступы в самом тексте можно убрать с помощью подстановки при присваивании.

    (my $message = <<"    END_MESSAGE") =~ s/^ {8}//gm; 
        ...
    END_MESSAGE

В подстановке мы заменяем 8 предстоящих пробелов на пустую строку. Мы ипользуем два модификатора: /m меняет поведение ^ таким образом, чтобы он соответствовал не началу строковой переменной, а началу каждой строки. /g указывает, что подстановка должна работать глобально, то есть повторяться до тех пор, пока возможно.

С этими двумя флагами подстановка уберем 8 предстоящих пробелов из каждой строки в переменной слева от =~. Необходимо заключить в скобки выражение слева от оператора, так как приоритет оператора присваивания (=) ниже, чем у оператора регулярного выражения =~. Без скобок perl бы сначала попытался применить регуляроне выражение к here-документу, что закончилось бы ошибкой на этапе компиляции:

Can't modify scalar in substitution (s///) at programming.pl line 9, near "s/^ {8}//gm;"

Использование q или qq вместо here-документов

После всех этих объяснений я уже не уверен, что мне стоит советовать вам использовать here-документы. Во многих случаях вместо них я использую оператор qq или q, в зависимости от того, нужно ли интерполировать переменные:

#!/usr/bin/perl
use strict;
use warnings;

my $name = 'Foo';
my $send = 1;

if ($send) {
    (my $message = qq{
        Дорогой $name,
    
        я хочу отправить тебе это сообщение.
    
        с наилучшими пожеланиями
          Perl Maven
        }) =~ s/^ {8}//mg;
    print $message;
}