На днях делал отсечение элементов списка не подходящих под регулярку введённую пользователем:
...
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 таит в себе множество тайн и когда уже думаешь, что знаешь о нём всё, оказывается, что ты лишь в начале долгого и увлекательного путешествия в мир кратких выражений.
Список использованной литературы
Пустой паттерн / https://perldoc.perl.org/perlop#The-empty-pattern-//
Проблема интерполяции паттерна / https://raku.org/archive/rfc/144.html
Повторяющиеся шаблоны поиска подстроки нулевой длины / https://metacpan.org/dist/POD2-RU/view/lib/POD2/RU/perlre.pod#Повторяющиеся-шаблоны-поиска-подстроки-нулевой-длины
Комментарии (10)
checkpoint
17.10.2021 22:28+4Это все сильно походит на баго-фичу. Т.е. на самом деле это бага, вызванная кривостью имплементации, но она была задокумнетирована и теперь является ценной фичей, вошедшей в оборот. И по этой самой причине исправлять её никто не собирается.
darviarush Автор
17.10.2021 22:36Да, было бы неплохо, чтобы perl обращал внимание на переменную внутри регулярки
/$re/
. Тем не менее теперь, когда я узнал о такой особенности, я её буду всегда учитывать и багов с ней недопущу.Deosis
18.10.2021 07:41+2А как подобное работает с многопоточностью? Последняя использованная регулярка одна на приложение или поток?
Если первое, то это кошмар, надеяться, что в другом потоке не будет вызвана регулярка.
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
Последняя использованная регулярка одна на поток.
insecto
18.10.2021 05:10+11Какое-то программирование из ночных кошмаров, когда всё насквозь мутабельное, всё на строках, и даже вызов чистой с виду функции мутирует какое-то невидимое состояние. Жуть!
zzzzzzzzzzzz
18.10.2021 18:28Это такая детская жуть.
Если хотите ощутить настоящие эмоции, советую попробовать писать CMD-шники для Windows. Вот это по-настоящему демоническая штука. Но при этом хитрая: сначала кажется, что там всё просто. А вот чем больше узнаёте, тем больше поглощает вас эта бездна...
Например, я на днях хотел написать скриптик и внезапно понял, что больше не знаю, как в CMD правильно сравнивать строки.
blind_oracle
18.10.2021 08:50+3Последний раз писал на Перле лет 10 назад и часто через месяц не мог прочитать что сам написал :) Особенно если налягать на контекстную переменную.
С тех пор его не трогал и волосы стали мягкими и шелковистыми :)
onix74
Испытываю слабость к perl. Необъяснимую. Есть в нём что-то... Правда, давно не доводилось им пользоваться и упоминаний о нем все меньше. Начал было думать, что он умер. Но две Ваши последние статьи вселили надежду. :-) Спасибо!
darviarush Автор
Perl не умер, его постоянно совершенствуют, выпускаются новые версии. Поддерживается инфраструктура. Проводятся конференции. Выпускаются журналы. В России есть вакансии perl-разработчиков, на нём написаны сайты крупных компаний и, как я упоминал в комментариях к одной из своих статей - появляются и стартапы.
Изучение же perl - осознанный выбор программиста, в то время, как изучение python или php - часто по рекомендации друга, что вот, мол - "там больше платят" или "его легче освоить".
onix74
Было время, когда он у нас был единственным вариантом "биллинга" интернет-трафика. Сами писали анализ логов squid и принятие решения по результатам. Эх! Классное время было! :-) Но, к сожалению, в данный момент как-то не находится ниши для перла. А жаль! С удовольствием бы его использовал.