Регулярные выражения Perl и их применение

Оператор split и функция grep

Разбить на страницы
Показывать лекцию целиком

14.1. Оператор split

Оператор разбиения split выполняет в каком-то смысле противоположную роль оператору поиска m/// с модификатором g в списковом контексте. Если этот оператор m/// возвращает все фрагменты текста, которые совпали с регулярным выражением, то оператор split возвращает фрагменты текста, которые не совпали с регулярным выражением, заданным ему в качестве аргумента. Оператор split разбивает исходный текст на куски, которые разделяются текстом, совпадающим с заданным регулярным выражением и возвращает эти куски текста в виде массива. В качестве разделителя может выступать регулярное выражение, которое совпадает только с позицией в тексте, например, оператор

@lines=split /^/m, $text;

вернет в массиве @lines заданный текст, разбитый на логические строки.

Замечу, что в регулярном выражении в операторе split якорь начала текста ^ означает начало логической строки, даже если модификатор m не задан, поэтому оператор

@lines=split /^/, $text;

будет работать точно так же, как и предыдущий оператор с модификатором m. Но можно это неявное правило отменить:

@lines=split /(?-m)^/, $text;

Это будет эквивалентно оператору

@lines=split /\A/, $text;

На метасимвол $ это неявное правило не распространяется.

В регулярном выражении, которое применяется в операторе split, точно так же работает интерполяция переменных и модификатор o, как и в обычных регулярных выражениях.

Оператор split можно использовать в простых случаях, например, когда надо разбить выходные данные от HTML-формы по знаку равенства, но этот оператор можно применять и в более сложных случаях. Однако, оператор split перегружен разными тонкостями и исключениями, которые надо рассмотреть. В общем случае этот оператор имеет вид

split  [ совпадение [ ,  целевой текст [ ,  ограничение  ] ]]

Список операндов при необходимости можно заключать в скобки. Третий операнд не обязателен. Если его значение равно нулю, то это эквивалентно тому, что он не задан.

Второй операнд также не обязателен, но в случае его отсутствия также должен отсутствовать и третий операнд. Не обязателен и первый операнд, оператор

split

эквивалентен вызову

split ' ', $_, 0

где первый операнд является строкой, состоящий из одного пробела. Этот вызов разобьет текст, находящийся в $_, по пробельным символам, при этом начальные и заключительные пустые элементы созданы не будут.

14.1.1. Специальный первый операнд // и ' '

Специальный первый операнд // (пустое регулярное выражение ) означает вовсе не применение регулярного выражения по умолчанию в качестве ограничителя для разбивки текста, а этот операнд разбивает входной текст на отдельные символы, из которых он состоит. Например, оператор

print join '-',split //, 'abcde';

напечатает a-b-c-d-e.

Специальный операнд ' ' (строка из одного пробела), разбивает заданный текст по пропускам \s+, но при этом начальные (и конечные) пробельные символы игнорируются.

Например, оператор

print join '-',split ' ', '       a      b     c      de       ';

напечатает a-b-c-de, а оператор

print join '-',split m/\s+/, '       a      b     c      de       ';

напечатает -a-b-c-de. Как видим, первый оператор игнорирует начальные и конечные пробелы, а второй - только конечные.

14.1.2. Первый операнд (совпадение)

Рассмотрим варианты для первого операндаоператора split. Это может быть

  • регулярное выражение;
  • объект регулярного выражения;
  • любое другое выражение, которое будет интерпретироваться как строка.
  • Для регулярных выражений вы можете употреблять лишь базовые модификаторы i, x, s, m, o. Если вы хотите сгруппировать подшаблоны, то используйте несохраняющие круглые скобки, т.к. захватывающие скобки имеют в этом операнде особый смысл.

    14.1.3. Второй операнд (целевой текст)

    Оператор split не изменяет целевой текст, поэтому в качестве целевого текста можно применять непосредственное значение. Если целевой текст не задан, то используется значение переменной по умолчанию $_.

    14.1.4. Третий операнд (ограничение)

    Роль этого необязательного операнда в том, чтобы ограничить число фрагментов, на которые будет разбит заданный текст. Если этот операнд отсутствует, то ограничения нет. Например, оператор

    print join '-', split ' ', '       a      b     c      de       ', 3;

    напечатает

    a-b-c      de

    После возврата двух элементов вернется остаток заданного текста. Если в результате текст разобьется на число частей, которое меньше заданного, то дополнительные элементы не создаются. Например:

    print join '-', split ' ', '       a      b     c      de       ', 10;

    Напечатается: a-b-c-de-. Мы видим, что в этой форме оператор split в отличие от случая, когда третий операнд отсутствует, начинает возвращать конечные пустые элементы.

    Также можно ограничить количество возвращаемых значений, если в качестве приемника задать список:

    ($a,$b,$c) = split /$re/, $text;

    После заполнения заданного количества полей Perl прекратит работу оператора split.

    Если количество кусков текста меньше заявленного количества элементов списка, то оставшиеся элементы списка получат пустые значения.

    14.1.4. Возвращение пустых элементов

    Если между совпадениями для первого операнда нет текста (совпадения идут подряд), то на соответствующих местах возвращаются пустые элементы. Оператор

    print join '-', split '=', 'a=b==c';

    напечатает

    a-b--c

    В этом случае пустые элементы, расположенные в конце текста, не возвращаются.

    Например, оператор

    print join '-',split '=', '===a=b==c===';

    напечатает

    ---a-b--c

    Для получения этих пустых элементов надо использвать третий операнд. Проще всего его установить в -1 (или в большое положительное число), тогда вернутся все элементы, на которые разбит текст. Оператор

    print join '-', split '=', '===a=b==c===', -1;

    напечатает

    ---a-b--c---

    Число -1 здесь эквивалентно бесконечно большому положительному числу.

    Иногда бывает нужно получить только непустые элементы разбиения текста. Для этого перед оператором split можно поставить вызов функции grep с аргументом length:

    print grep { length } split '=', '===a=b==c===', -1;

    или вместо length можно подставить само возвращаемое в $_ split значение:

    print grep $_, split '=', '===a=b==c===', -1;

    Напечатается просто abc. К каждому элементу списка, возвращаемому оператором split, будет применена функция length. И если результат будет равен нулю, то этот элемент будет удален из данного списка.

    Мы уже видели, что пустые фрагменты, совпавшие в начале текста, возвращаются. Но если совпадение было без поглощения текста, а чисто позиционным, то эти пустые фрагменты не возвращаются. Например, оператор

    print join '-', split /(?=\w)/, "abcd";

    напечатает a-b-c-d, хотя перед буквой a тоже было совпадение.

    Обращаю ваше внимание, что оператор

    split /^/m, $text;

    вернет все логические строки, в том числе и пустые: ведь при совпадении с пустыми строками возвращаются элементы \n, которые сами не являются пустыми.

    14.1.5. Отсутствие побочных эффектов у оператора split

    Оператор split не имеет побочных эффектов: он не изменяет регулярное выражение по умолчанию, не устанавливает специальные переменные $1, …, $`, $, $' и т.д. Существует лишь один побочный эффект, не связанный с регулярными выражениями: при использовании оператора split в скалярном контексте он записывает возвращаемый результа в массив @_, который также используется для передачи параметров функциям.

    При применении директивы use warnings или параметра -w при запуске Perl, выдается соответствующее предупреждение:

    #!/usr/bin/perl -w
    use strict;
    
    scalar split /(?=\w)/, "abcd";
    print @_;

    Напечатается

    Use of implicit split to @_ is deprecated at z.pl line 4.
    abcd

    Сказанное об отсутствии побочных эффектов не означает, что внутри регулярного выражения нумерованные переменные не создаются. Если их распечатать во встроенном коде Perl, то мы увидим их значение. Просто после окончания работы регулярного выражения восстанавливаются старые значения всех специальных переменных регулярных выражений. Например, оператор

    split /(\w)(?{ print "=$1=" })/, "abcd";

    напечатает текст

    =a==b==c==d=

    14.1.5. Захватывающие скобки в первом операнде оператора split

    Если в первом операнде оператора split имеются захватывающие скобки, то фрагменты текста, которые они захватили, также вставляютя в результирующий список между фрагментами текста, на которые разбился заданный текст. Эти фрагменты, совпавшие с захватывающими скобками, не учитываются в общем числе возвращаемых фрагментов, т.к. третий операнд определяет не число возвращаемых фрагментов, а количество фрагментов, на которые разбивается исходный текст. Если какая-либо пара захватывающих скобок не участвовала в совпадении, то она возвращает значение undef.

    Пример:

    print join '-', split /(\d+)/, 'a12b23c34d45e56', 3;

    Напечатается

    a-12-b-23-c34d45e56

    т.е. числа, по которым разбивался текст, заносятся в результат, чередуясь с самими фрагментами, на которые был разбит исходный текст. Текст был разбит на три части.

    Вот еще пример - HTML-текст разбивается на текст вне тегов и при этом возвращаются все теги:

    $_=<<EOD;
    <b>жирный текст</b>
    обычный текст
    <i>курсив</i>
    <h1> заголовок </h1>
    обычный текст
    EOD
    
    print join '-', split /(<[^>]+>)/;

    Будет напечатано:

    -<b>-жирный текст-</b>-
    обычный текст
    -<i>-курсив-</i>-
    -<h1>- заголовок -</h1>-
    обычный текст

    Оператор split обычно используется при получении данных от форм HTML. Ниже приведен пример стандартной программы, которая принимает формы, отправленные по методу GET и POST, и записывает имена и значения переменных в ассоциативный хеш contents:

    if ($ENV{'REQUEST_METHOD'} eq 'POST') 
       # Принимаем данные по методу POST
    { # Читаем переданные формой данные 
      read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
      # Разбиваем их на пары имя=значение
      @pairs=split(//, $buffer);
      foreach $pair (@pairs)
       { # Каждую пару разбиваем на имя и значение
         ($name,$value)=split(/=/,$pair);
         # Заменяем плюсы на пробелы (браузеры кодируют пробелы плюсами)
         $value =~ tr/+/ /;
         # Заменяем 16-ные байты их значениями
         $value =~ s/%([a-fA-F0-9][a-fA-f0-9])/pack("C",hex($1))/eg;
         # Записываем результат в хеш
         $contents{$name}=$value;
       }
    } elsif ($ENV{'REQUEST_METHOD'} eq 'GET')
          # Принимаем данные по методу GET
       { @pairs=split(//, $ENV{QUERY_STRING});
         foreach $pair (@pairs)
          { ($name,$value)=split(/=/,$pair);
            $value =~ tr/+/ /;
            $value =~ s/%([a-fA-F0-9][a-fA-f0-9])/pack("C",hex($1))/eg;
            $contents{$name}=$value;
          }
       }
    
    print "Content-Type: text/html\n\n";
    print '<html><body>OK</body></html>';

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

    14.1. Функция grep

    Функция grep является мощным инструментом для создания из списков подсписков по определенным критериям. Эта мощь обусловлена тем фактом, что данная функция может использовать регулярные выражения. Модификаторы этого регулярного выражения те же, что и у оператора split. Функция grep имеет две разновидности вызова:

    grep { блок команд } список

    и

    grep выражение, список

    Эта функция в неявном цикле по всем элементам списка для каждого из них выполняет заданный блок команд или выражение. Причем при каждой итерации цикла во время выполнения блока команд или выражения переменная $_ является синонимом очередного элемента списка. В частности, присвоение этой переменной значения повлечет присвоение этого же значения соответствующему элементу списка. Функция в качестве результата возвращает подсписок, для которых вычисленное значение выражения/блока команд является истиной (отлично от нуля, пустой строки и т.д.). Например, код

    open F, 'prog.pl';
    print grep { !/^#|^\s*$/m } <F>;
    close F;

    распечатает все строки файла prog.pl, которые не начинаются с символа # и не содержат одни лишь пробельные символы.

    Страницы:

    14.1. Оператор split

    Оператор разбиения split выполняет в каком-то смысле противоположную роль оператору поиска m/// с модификатором g в списковом контексте. Если этот оператор m/// возвращает все фрагменты текста, которые совпали с регулярным выражением, то оператор split возвращает фрагменты текста, которые не совпали с регулярным выражением, заданным ему в качестве аргумента. Оператор split разбивает исходный текст на куски, которые разделяются текстом, совпадающим с заданным регулярным выражением и возвращает эти куски текста в виде массива. В качестве разделителя может выступать регулярное выражение, которое совпадает только с позицией в тексте, например, оператор

    @lines=split /^/m, $text;

    вернет в массиве @lines заданный текст, разбитый на логические строки.

    Замечу, что в регулярном выражении в операторе split якорь начала текста ^ означает начало логической строки, даже если модификатор m не задан, поэтому оператор

    @lines=split /^/, $text;

    будет работать точно так же, как и предыдущий оператор с модификатором m. Но можно это неявное правило отменить:

    @lines=split /(?-m)^/, $text;

    Это будет эквивалентно оператору

    @lines=split /\A/, $text;

    На метасимвол $ это неявное правило не распространяется.

    В регулярном выражении, которое применяется в операторе split, точно так же работает интерполяция переменных и модификатор o, как и в обычных регулярных выражениях.

    Оператор split можно использовать в простых случаях, например, когда надо разбить выходные данные от HTML-формы по знаку равенства, но этот оператор можно применять и в более сложных случаях. Однако, оператор split перегружен разными тонкостями и исключениями, которые надо рассмотреть. В общем случае этот оператор имеет вид

    split  [ совпадение [ ,  целевой текст [ ,  ограничение  ] ]]

    Список операндов при необходимости можно заключать в скобки. Третий операнд не обязателен. Если его значение равно нулю, то это эквивалентно тому, что он не задан.

    Второй операнд также не обязателен, но в случае его отсутствия также должен отсутствовать и третий операнд. Не обязателен и первый операнд, оператор

    split

    эквивалентен вызову

    split ' ', $_, 0

    где первый операнд является строкой, состоящий из одного пробела. Этот вызов разобьет текст, находящийся в $_, по пробельным символам, при этом начальные и заключительные пустые элементы созданы не будут.

    14.1.1. Специальный первый операнд // и ' '

    Специальный первый операнд // (пустое регулярное выражение ) означает вовсе не применение регулярного выражения по умолчанию в качестве ограничителя для разбивки текста, а этот операнд разбивает входной текст на отдельные символы, из которых он состоит. Например, оператор

    print join '-',split //, 'abcde';

    напечатает a-b-c-d-e.

    Специальный операнд ' ' (строка из одного пробела), разбивает заданный текст по пропускам \s+, но при этом начальные (и конечные) пробельные символы игнорируются.

    Например, оператор

    print join '-',split ' ', '       a      b     c      de       ';

    напечатает a-b-c-de, а оператор

    print join '-',split m/\s+/, '       a      b     c      de       ';

    напечатает -a-b-c-de. Как видим, первый оператор игнорирует начальные и конечные пробелы, а второй - только конечные.

    14.1.2. Первый операнд (совпадение)

    Рассмотрим варианты для первого операндаоператора split. Это может быть

  • регулярное выражение;
  • объект регулярного выражения;
  • любое другое выражение, которое будет интерпретироваться как строка.
  • Для регулярных выражений вы можете употреблять лишь базовые модификаторы i, x, s, m, o. Если вы хотите сгруппировать подшаблоны, то используйте несохраняющие круглые скобки, т.к. захватывающие скобки имеют в этом операнде особый смысл.

    14.1.3. Второй операнд (целевой текст)

    Оператор split не изменяет целевой текст, поэтому в качестве целевого текста можно применять непосредственное значение. Если целевой текст не задан, то используется значение переменной по умолчанию $_.

    14.1.4. Третий операнд (ограничение)

    Роль этого необязательного операнда в том, чтобы ограничить число фрагментов, на которые будет разбит заданный текст. Если этот операнд отсутствует, то ограничения нет. Например, оператор

    print join '-', split ' ', '       a      b     c      de       ', 3;

    напечатает

    a-b-c      de

    После возврата двух элементов вернется остаток заданного текста. Если в результате текст разобьется на число частей, которое меньше заданного, то дополнительные элементы не создаются. Например:

    print join '-', split ' ', '       a      b     c      de       ', 10;

    Напечатается: a-b-c-de-. Мы видим, что в этой форме оператор split в отличие от случая, когда третий операнд отсутствует, начинает возвращать конечные пустые элементы.

    Также можно ограничить количество возвращаемых значений, если в качестве приемника задать список:

    ($a,$b,$c) = split /$re/, $text;

    После заполнения заданного количества полей Perl прекратит работу оператора split.

    Если количество кусков текста меньше заявленного количества элементов списка, то оставшиеся элементы списка получат пустые значения.

    14.1.4. Возвращение пустых элементов

    Если между совпадениями для первого операнда нет текста (совпадения идут подряд), то на соответствующих местах возвращаются пустые элементы. Оператор

    print join '-', split '=', 'a=b==c';

    напечатает

    a-b--c

    В этом случае пустые элементы, расположенные в конце текста, не возвращаются.

    Например, оператор

    print join '-',split '=', '===a=b==c===';

    напечатает

    ---a-b--c

    Для получения этих пустых элементов надо использвать третий операнд. Проще всего его установить в -1 (или в большое положительное число), тогда вернутся все элементы, на которые разбит текст. Оператор

    print join '-', split '=', '===a=b==c===', -1;

    напечатает

    ---a-b--c---

    Число -1 здесь эквивалентно бесконечно большому положительному числу.

    Иногда бывает нужно получить только непустые элементы разбиения текста. Для этого перед оператором split можно поставить вызов функции grep с аргументом length:

    print grep { length } split '=', '===a=b==c===', -1;

    или вместо length можно подставить само возвращаемое в $_ split значение:

    print grep $_, split '=', '===a=b==c===', -1;

    Напечатается просто abc. К каждому элементу списка, возвращаемому оператором split, будет применена функция length. И если результат будет равен нулю, то этот элемент будет удален из данного списка.

    Мы уже видели, что пустые фрагменты, совпавшие в начале текста, возвращаются. Но если совпадение было без поглощения текста, а чисто позиционным, то эти пустые фрагменты не возвращаются. Например, оператор

    print join '-', split /(?=\w)/, "abcd";

    напечатает a-b-c-d, хотя перед буквой a тоже было совпадение.

    Обращаю ваше внимание, что оператор

    split /^/m, $text;

    вернет все логические строки, в том числе и пустые: ведь при совпадении с пустыми строками возвращаются элементы \n, которые сами не являются пустыми.

    14.1.5. Отсутствие побочных эффектов у оператора split

    Оператор split не имеет побочных эффектов: он не изменяет регулярное выражение по умолчанию, не устанавливает специальные переменные $1, …, $`, $, $' и т.д. Существует лишь один побочный эффект, не связанный с регулярными выражениями: при использовании оператора split в скалярном контексте он записывает возвращаемый результа в массив @_, который также используется для передачи параметров функциям.

    При применении директивы use warnings или параметра -w при запуске Perl, выдается соответствующее предупреждение:

    #!/usr/bin/perl -w
    use strict;
    
    scalar split /(?=\w)/, "abcd";
    print @_;

    Напечатается

    Use of implicit split to @_ is deprecated at z.pl line 4.
    abcd

    Сказанное об отсутствии побочных эффектов не означает, что внутри регулярного выражения нумерованные переменные не создаются. Если их распечатать во встроенном коде Perl, то мы увидим их значение. Просто после окончания работы регулярного выражения восстанавливаются старые значения всех специальных переменных регулярных выражений. Например, оператор

    split /(\w)(?{ print "=$1=" })/, "abcd";

    напечатает текст

    =a==b==c==d=

    14.1.5. Захватывающие скобки в первом операнде оператора split

    Если в первом операнде оператора split имеются захватывающие скобки, то фрагменты текста, которые они захватили, также вставляютя в результирующий список между фрагментами текста, на которые разбился заданный текст. Эти фрагменты, совпавшие с захватывающими скобками, не учитываются в общем числе возвращаемых фрагментов, т.к. третий операнд определяет не число возвращаемых фрагментов, а количество фрагментов, на которые разбивается исходный текст. Если какая-либо пара захватывающих скобок не участвовала в совпадении, то она возвращает значение undef.

    Пример:

    print join '-', split /(\d+)/, 'a12b23c34d45e56', 3;

    Напечатается

    a-12-b-23-c34d45e56

    т.е. числа, по которым разбивался текст, заносятся в результат, чередуясь с самими фрагментами, на которые был разбит исходный текст. Текст был разбит на три части.

    Вот еще пример - HTML-текст разбивается на текст вне тегов и при этом возвращаются все теги:

    $_=<<EOD;
    <b>жирный текст</b>
    обычный текст
    <i>курсив</i>
    <h1> заголовок </h1>
    обычный текст
    EOD
    
    print join '-', split /(<[^>]+>)/;

    Будет напечатано:

    -<b>-жирный текст-</b>-
    обычный текст
    -<i>-курсив-</i>-
    -<h1>- заголовок -</h1>-
    обычный текст

    Оператор split обычно используется при получении данных от форм HTML. Ниже приведен пример стандартной программы, которая принимает формы, отправленные по методу GET и POST, и записывает имена и значения переменных в ассоциативный хеш contents:

    if ($ENV{'REQUEST_METHOD'} eq 'POST') 
       # Принимаем данные по методу POST
    { # Читаем переданные формой данные 
      read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
      # Разбиваем их на пары имя=значение
      @pairs=split(//, $buffer);
      foreach $pair (@pairs)
       { # Каждую пару разбиваем на имя и значение
         ($name,$value)=split(/=/,$pair);
         # Заменяем плюсы на пробелы (браузеры кодируют пробелы плюсами)
         $value =~ tr/+/ /;
         # Заменяем 16-ные байты их значениями
         $value =~ s/%([a-fA-F0-9][a-fA-f0-9])/pack("C",hex($1))/eg;
         # Записываем результат в хеш
         $contents{$name}=$value;
       }
    } elsif ($ENV{'REQUEST_METHOD'} eq 'GET')
          # Принимаем данные по методу GET
       { @pairs=split(//, $ENV{QUERY_STRING});
         foreach $pair (@pairs)
          { ($name,$value)=split(/=/,$pair);
            $value =~ tr/+/ /;
            $value =~ s/%([a-fA-F0-9][a-fA-f0-9])/pack("C",hex($1))/eg;
            $contents{$name}=$value;
          }
       }
    
    print "Content-Type: text/html\n\n";
    print '<html><body>OK</body></html>';

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

    14.1. Функция grep

    Функция grep является мощным инструментом для создания из списков подсписков по определенным критериям. Эта мощь обусловлена тем фактом, что данная функция может использовать регулярные выражения. Модификаторы этого регулярного выражения те же, что и у оператора split. Функция grep имеет две разновидности вызова:

    grep { блок команд } список

    и

    grep выражение, список

    Эта функция в неявном цикле по всем элементам списка для каждого из них выполняет заданный блок команд или выражение. Причем при каждой итерации цикла во время выполнения блока команд или выражения переменная $_ является синонимом очередного элемента списка. В частности, присвоение этой переменной значения повлечет присвоение этого же значения соответствующему элементу списка. Функция в качестве результата возвращает подсписок, для которых вычисленное значение выражения/блока команд является истиной (отлично от нуля, пустой строки и т.д.). Например, код

    open F, 'prog.pl';
    print grep { !/^#|^\s*$/m } <F>;
    close F;

    распечатает все строки файла prog.pl, которые не начинаются с символа # и не содержат одни лишь пробельные символы.

    Вернуться к учебному плану