На днях делал отсечение элементов списка не подходящих под регулярку введённую пользователем:

...
my $re = get_text_in_filter();
@list = grep { /$re/i } @list;
...

$re был пустой строкой и в @list должны были остаться все элементы.

Так и происходило при первом проходе, а при втором регулярка не пропускала ни одного элемента списка.

Я предположил, что $re или @list изменились и распечатал их датапринтером:

use DDP;
p my $x=[$re, qr/$re/, $list[0], $list[0] =~ qr/$re/? "ok":"no")];

# 1-й проход:
[
    [0] "",
    [1]   (modifiers: u),
    [2] "Application",
    [3] "ok"
]
# 2-й проход:
[
    [0] "",
    [1]   (modifiers: u),
    [2] "Application",
    [3] "no"
]

С точки зрения датапринтера они не изменились. "В таком случае, возможно, в переменных какой-то флаг был установлен", продолжал рассуждать я. И сдампил $x дэвэл-пиком:

pop @$x;
use Devel::Peek;
Dump($x);

# 1-й проход:
SV = IV(0x56228695d7f8) at 0x56228695d808
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x56228708a930
  SV = PVAV(0x56228712b618) at 0x56228708a930
    REFCNT = 1
    FLAGS = ()
    ARRAY = 0x562287095590
    FILL = 2
    MAX = 3
    FLAGS = (REAL)
    Elt No. 0
    SV = PV(0x56228708c200) at 0x562286bfdbb0
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x562286fd9930 ""\0
      CUR = 0
      LEN = 10
    Elt No. 1
    SV = IV(0x5622870ac428) at 0x5622870ac438
      REFCNT = 1
      FLAGS = (ROK)
      RV = 0x562286c0ef68
      SV = REGEXP(0x56228710d4a0) at 0x562286c0ef68
        REFCNT = 1
        FLAGS = (OBJECT,POK,FAKE,pPOK)
        PV = 0x5622870c04d0 "(?^u:)"
        CUR = 6
        LEN = 0
        STASH = 0x562285ffd230  "Regexp"
        COMPFLAGS = 0x100 ()
        EXTFLAGS = 0x80000100 (NULL)
        ENGINE = 0x7f817615d380 (STANDARD)
        INTFLAGS = 0x0 ()
        NPARENS = 0
        LASTPAREN = 0
        LASTCLOSEPAREN = 0
        MINLEN = 0
        MINLENRET = 0
        GOFS = 0
        PRE_PREFIX = 5
        SUBLEN = 0
        SUBOFFSET = 0
        SUBCOFFSET = 0
        SUBBEG = 0x0
        MOTHER_RE = 0x562287123810
        SV = REGEXP(0x56228710d3e0) at 0x562287123810
          REFCNT = 2
          FLAGS = (POK,pPOK)
          PV = 0x5622870c04d0 "(?^u:)"
          CUR = 6
          LEN = 10
          COMPFLAGS = 0x100 ()
          EXTFLAGS = 0x80000100 (NULL)
          ENGINE = 0x7f817615d380 (STANDARD)
          INTFLAGS = 0x0 ()
          NPARENS = 0
          LASTPAREN = 0
          LASTCLOSEPAREN = 0
          MINLEN = 0
          MINLENRET = 0
          GOFS = 0
          PRE_PREFIX = 5
          SUBLEN = 0
          SUBOFFSET = 0
          SUBCOFFSET = 0
          SUBBEG = 0x0
          MOTHER_RE = 0x0
          PAREN_NAMES = 0x0
          SUBSTRS = 0x562287042a00
          PPRIVATE = 0x562287130bb0
          OFFS = 0x5622870153f0
          QR_ANONCV = 0x0
          SAVED_COPY = 0x0
        PAREN_NAMES = 0x0
        SUBSTRS = 0x562287042970
        PPRIVATE = 0x562287130bb0
        OFFS = 0x562287095090
        QR_ANONCV = 0x0
        SAVED_COPY = 0x0
    Elt No. 2
    SV = PVNV(0x5622870402a0) at 0x562286bfdd00
      REFCNT = 1
      FLAGS = (POK,IsCOW,pPOK)
      IV = 0
      NV = 0
      PV = 0x562287130eb0 "Application"\0
      CUR = 11
      LEN = 13
      COW_REFCNT = 3
      
# 2-й проход:
SV = IV(0x56228695d7f8) at 0x56228695d808
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x562287058898
  SV = PVAV(0x562287139a90) at 0x562287058898
    REFCNT = 1
    FLAGS = ()
    ARRAY = 0x5622871cb700
    FILL = 2
    MAX = 3
    FLAGS = (REAL)
    Elt No. 0
    SV = PV(0x56228713b690) at 0x56228712e378
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x562287113620 ""\0
      CUR = 0
      LEN = 10
    Elt No. 1
    SV = IV(0x5622871c7be0) at 0x5622871c7bf0
      REFCNT = 1
      FLAGS = (ROK)
      RV = 0x5622871419b8
      SV = REGEXP(0x56228710d860) at 0x5622871419b8
        REFCNT = 1
        FLAGS = (OBJECT,POK,FAKE,pPOK)
        PV = 0x5622870c04d0 "(?^u:)"
        CUR = 6
        LEN = 0
        STASH = 0x562285ffd230  "Regexp"
        COMPFLAGS = 0x100 ()
        EXTFLAGS = 0x80000100 (NULL)
        ENGINE = 0x7f817615d380 (STANDARD)
        INTFLAGS = 0x0 ()
        NPARENS = 0
        LASTPAREN = 0
        LASTCLOSEPAREN = 0
        MINLEN = 0
        MINLENRET = 0
        GOFS = 0
        PRE_PREFIX = 5
        SUBLEN = 0
        SUBOFFSET = 0
        SUBCOFFSET = 0
        SUBBEG = 0x0
        MOTHER_RE = 0x562287123810
        SV = REGEXP(0x56228710d3e0) at 0x562287123810
          REFCNT = 2
          FLAGS = (POK,pPOK)
          PV = 0x5622870c04d0 "(?^u:)"
          CUR = 6
          LEN = 10
          COMPFLAGS = 0x100 ()
          EXTFLAGS = 0x80000100 (NULL)
          ENGINE = 0x7f817615d380 (STANDARD)
          INTFLAGS = 0x0 ()
          NPARENS = 0
          LASTPAREN = 0
          LASTCLOSEPAREN = 0
          MINLEN = 0
          MINLENRET = 0
          GOFS = 0
          PRE_PREFIX = 5
          SUBLEN = 0
          SUBOFFSET = 0
          SUBCOFFSET = 0
          SUBBEG = 0x0
          MOTHER_RE = 0x0
          PAREN_NAMES = 0x0
          SUBSTRS = 0x562287042a00
          PPRIVATE = 0x562287130bb0
          OFFS = 0x5622870153f0
          QR_ANONCV = 0x0
          SAVED_COPY = 0x0
        PAREN_NAMES = 0x0
        SUBSTRS = 0x5622871b0af0
        PPRIVATE = 0x562287130bb0
        OFFS = 0x562287093f70
        QR_ANONCV = 0x0
        SAVED_COPY = 0x0
    Elt No. 2
    SV = PVNV(0x562287040580) at 0x5622871cdc18
      REFCNT = 1
      FLAGS = (POK,IsCOW,pPOK)
      IV = 0
      NV = 0
      PV = 0x562286fe39d0 "Application"\0
      CUR = 11
      LEN = 13
      COW_REFCNT = 3

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

"В таком случае дело в какой-то внутренней переменной perl-а, которая влияет на поведение пустой регулярки", решил я.

Я уж думал поднять исходники perl-а, но для начала загуглил. Оказалось, что данное поведение регулярок описано в perlop. А именно у оператора матчинга (=~): оказывается, при использовании регулярка запоминается perl-ом, а пустая регулярка матчит как предыдущая. Что видно на следующем примере:

$x = "abc";
$y = "xby";
$x =~ /b/; print $`;  # -> a
$y =~ //; print $`;   # -> x

Причём предыдущая регулярка может быть использована где угодно:

sub f1 { $_[0] =~ /b/; print $`; }
sub f2 { f1(@_) }
$x = "abc";
$y = "xby";
f2($x);             # -> a
$y =~ //; print $`; # -> x

То есть между двумя проходами отбора элементов списка где-то была использована регулярка, которая запомнилась и была использована пустой регуляркой. Проблема в том, что пустая это регулярка или нет perl определяет не на стадии компиляции. То есть он вначале интерполирует регулярку, а потом уже судит:

$x = "abc";
$y = "xby";
$x =~ /b/; print $`;                # -> a
my $re = ""; $y =~ /$re/; print $`; # -> x

Но если переменная $re не пустая:

$x = "abc";
$y = "xby";
$x =~ /b/; print $`;                 # -> a
my $re = "y"; $y =~ /$re/; print $`; # -> xb

В perlop предлагается ставить () при использовании с переменной которая может быть пустой:

$x = "abc";
$_ = "xby";
$x =~ /b/;
my $re = ""; $y =~ /()$re/; print $`; # ->
print $'; # -> xby

Из особенностей пустой регулярки ещё нужно сказать о том, что она использует предыдущую регулярку не только в матчинге, но и при замене и не работает так в split (а просто бъёт строку на символы):

$x = "abc";
$_ = "xby";
$x =~ /b/;
s//*/;
print;  # -> x*y
$, = ", "; print split //, "abc"; # -> a, b, c

И ещё, что с эскейпингом /\Q$re\E/ она тоже считается пустой:

$x = "abc";
$y = "xby";
$x =~ /b/; print $`; # -> a
my $re = ""; $y =~ /\Q$re\E/; print $`; # -> x

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

Список использованной литературы

  1. Пустой паттерн / https://perldoc.perl.org/perlop#The-empty-pattern-//

  2. Проблема интерполяции паттерна / https://raku.org/archive/rfc/144.html

  3. Повторяющиеся шаблоны поиска подстроки нулевой длины / https://metacpan.org/dist/POD2-RU/view/lib/POD2/RU/perlre.pod#Повторяющиеся-шаблоны-поиска-подстроки-нулевой-длины

Комментарии (10)


  1. onix74
    17.10.2021 22:05
    +5

    Испытываю слабость к perl. Необъяснимую. Есть в нём что-то... Правда, давно не доводилось им пользоваться и упоминаний о нем все меньше. Начал было думать, что он умер. Но две Ваши последние статьи вселили надежду. :-) Спасибо!


    1. darviarush Автор
      17.10.2021 22:27
      +1

      Perl не умер, его постоянно совершенствуют, выпускаются новые версии. Поддерживается инфраструктура. Проводятся конференции. Выпускаются журналы. В России есть вакансии perl-разработчиков, на нём написаны сайты крупных компаний и, как я упоминал в комментариях к одной из своих статей - появляются и стартапы.

      Изучение же perl - осознанный выбор программиста, в то время, как изучение python или php - часто по рекомендации друга, что вот, мол - "там больше платят" или "его легче освоить".


      1. onix74
        17.10.2021 22:37

        Было время, когда он у нас был единственным вариантом "биллинга" интернет-трафика. Сами писали анализ логов squid и принятие решения по результатам. Эх! Классное время было! :-) Но, к сожалению, в данный момент как-то не находится ниши для перла. А жаль! С удовольствием бы его использовал.


  1. checkpoint
    17.10.2021 22:28
    +4

    Это все сильно походит на баго-фичу. Т.е. на самом деле это бага, вызванная кривостью имплементации, но она была задокумнетирована и теперь является ценной фичей, вошедшей в оборот. И по этой самой причине исправлять её никто не собирается.


    1. darviarush Автор
      17.10.2021 22:36

      Да, было бы неплохо, чтобы perl обращал внимание на переменную внутри регулярки /$re/. Тем не менее теперь, когда я узнал о такой особенности, я её буду всегда учитывать и багов с ней недопущу.


      1. Deosis
        18.10.2021 07:41
        +2

        А как подобное работает с многопоточностью? Последняя использованная регулярка одна на приложение или поток?

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


        1. darviarush Автор
          20.10.2021 20:18

          Долго ли проверить? )

          perl -e 'use threads ("yield",
                       "stack_size" => 64*4096,
                       "exit" => "threads_only",
                       "stringify"); 
            @x = map { 
              my $x=$_; 
              threads->create(sub { 
                 /$x/; sleep 3-$x; print "$x) ", $x=~//? 1:0, "\n"; 
              }) 
            } 1..2; 
            $_->join for @x'
          2) 1
          1) 1

          Последняя использованная регулярка одна на поток.


  1. insecto
    18.10.2021 05:10
    +11

    Какое-то программирование из ночных кошмаров, когда всё насквозь мутабельное, всё на строках, и даже вызов чистой с виду функции мутирует какое-то невидимое состояние. Жуть!


    1. zzzzzzzzzzzz
      18.10.2021 18:28

      Это такая детская жуть.

      Если хотите ощутить настоящие эмоции, советую попробовать писать CMD-шники для Windows. Вот это по-настоящему демоническая штука. Но при этом хитрая: сначала кажется, что там всё просто. А вот чем больше узнаёте, тем больше поглощает вас эта бездна...

      Например, я на днях хотел написать скриптик и внезапно понял, что больше не знаю, как в CMD правильно сравнивать строки.


  1. blind_oracle
    18.10.2021 08:50
    +3

    Последний раз писал на Перле лет 10 назад и часто через месяц не мог прочитать что сам написал :) Особенно если налягать на контекстную переменную.

    С тех пор его не трогал и волосы стали мягкими и шелковистыми :)