2010-08-24 3 views
7

Jak mogę usunąć przechwytywanie z dowolnie zagnieżdżonych podgrup w ciągu regex Perla? Chciałbym zagnieździć dowolny regex w wyrażeniu otaczającym, które przechwytuje podregex jako całość, a także statycznie znane kolejne grupy. Czy muszę przekształcić ciąg regex ręcznie na wszystkie grupy nie przechwytujące (?:) (i mam nadzieję, że nie zepsuć), czy istnieje mechanizm regex lub biblioteka Perl, który zapewnia to?Jak utworzyć dowolne wyrażenie regularne Perla w całości nie przechwytujące? (Odpowiedź: Nie możesz)

# How do I 'flatten' $regex to protect $2 and $3? 
# Searching 'ABCfooDE' for 'foo' OK, but '((B|(C))fo(o)?(?:D|d)?)', etc., breaks. 
# I.E., how would I turn it effectively into '(?:(?:B|(?:C))fo(?:o)?(?:D|d)?)'? 
sub check { 
    my($line, $regex) = @_; 
    if ($line =~ /(^.*)($regex)(.*$)/) { 
    print "<", $1, "><", $2, "><", $3, ">\n"; 
    } 
} 

Uzupełnienie: Jestem niejasno świadomy $&, $` i $' i zostali poinformowani ich unikać, jeśli to możliwe, i nie mam dostępu do ${^PREMATCH}, ${^MATCH} i ${^POSTMATCH} w moim środowisku Perl 5.8 . Powyższy przykład można podzielić na partycje 2/3 za pomocą metod takich jak te, a bardziej złożone rzeczywiste przypadki można ręcznie iterować to, ale myślę, że chciałbym ogólne rozwiązanie, jeśli to możliwe.

Zaakceptowany Odpowiedź: co chcę istniał i niespodziewanie (przynajmniej dla mnie) nie jest grupą hermetyzującego sprawia, że ​​jego zawartość nieprzejrzysty, tak że kolejne pozycyjne wsteczne zobaczyć zawartość jako pojedynczy podmiot i nazwy odnośniki o wyznaczonym zakresie. gbacon ma potencjalnie użyteczne obejście dla Perla 5.10+, a FM pokazuje mechanizm iteracyjny dla każdej wersji, która może osiągnąć ten sam efekt w określonych przypadkach, ale j_random_hacker nazywa to brakiem mechanizmu języka rzeczywistego do enkapsulacji podwyrażeń.

+1

+1 Dobra robota z kolejnymi zmianami. – FMc

Odpowiedz

8

Ogólnie rzecz biorąc, nie można.

Nawet jeśli można przekształcić wszystkie (...) s do (?:...) s, to nie będzie działać w ogólnym przypadku, ponieważ wzór może wymagać wstecznych: np /(.)X\1/, który pasuje do dowolnego znaku, po którym następuje X, a następnie pierwotnie dopasowana postać.

Tak więc, w przypadku braku mechanizmu Perla do odrzucania przechwyconych wyników "po fakcie", nie ma sposobu na rozwiązanie problemu dla wszystkich wyrażeń regularnych. Najlepsze co możesz zrobić (lub możesz zrobić, gdybyś miał Perla 5.10), to użyć gbacon's suggestion i mieć nadzieję na wygenerowanie unikalnej nazwy bufora przechwytywania.

0

nie wyłączyć przechwytywania, ale może osiągnąć to, co chcesz:

$ perl -wle 'my $_ = "123abc"; /(\d+)/ && print "num: $1"; { /([a-z]+)/ && print "letter: $1"; } print "num: $1";' 
num: 123 
letter: abc 
num: 123 

utworzyć nowy zakres i $ 1 na zewnątrz nie zostaną naruszone.

7

Jednym ze sposobów ochrony podwzorów Ci zależy jest użycie named capture buffers:

Dodatkowo, od Perl 5.10.0 można używać nazwanych bufory przechwytywania i nazwał odwołania wstecznego. Notacja jest (?<name>...) do zadeklarowania i \k<name> do odniesienia. Możesz również użyć apostrofów zamiast nawiasów ostrych, aby ograniczyć nazwę; i możesz użyć składni referencyjnej wstecznej. Możliwe jest również odwoływanie się do nazwanego bufora przechwytywania przez bezwzględną i względną liczbę. Poza wzorcem nazwany bufor przechwytywania jest dostępny za pośrednictwem skrótu %+. Gdy różne bufory w tym samym wzorze mają tę samą nazwę, odnoszą się do skrajnie lewej grupy zdefiniowanej.

W kontekście pytania, check staje

sub check { 
    use 5.10.0; 
    my($line, $regex) = @_; 
    if ($line =~ /(^.*)($regex)(.*$)/) { 
    print "<", $+{one}, "><", $+{two}, "><", $+{three}, ">\n"; 
    } 
} 

Następnie nazywając go

my $pat = qr/(?<one>(?<two>B|(?<three>C))fo(o)?(?:D|d)?)/; 
check "ABCfooDE", $pat; 

wyjść

<CfooD><C><C>
+0

Jest to zgrabna technika, której nie znałem, ale niestety utknąłem w środowisku RHEL 4 (Perl v5.8.5), więc nie mogę jej użyć na razie. – Jeff

5

ten nie dotyczy przypadku ogólnym, ale Twój konkretny przykład można obsłużyć z opcją /g w kontekście skalarnym, które pozwalają podzielić problem na dwa mecze, drugi zbierając gdzie pierwszy przerwał:

sub check { 
    my($line, $regex) = @_; 
    my ($left_side, $regex_match) = ($1, $2) if $line =~ /(^.*)($regex)/g; 
    my $right_side = $1 if $line =~ /(.*$)/g; 
    print "<$left_side> <$regex_match> <$right_side>\n"; # <AB> <CfooD> <E123> 
} 

check('ABCfooDE123', qr/((B|(C))fo(o)?(?:D|d)?)/); 
+0

Dzięki, ta technika jest prawdopodobnie wystarczająco dobra, abym mógł ją teraz wykorzystać do moich rzeczywistych przypadków użycia. Myślę, że ostatecznie potrzebuję bardziej ogólnego rozwiązania, więc mam zamiar pozostać otwarte pytanie. – Jeff

2

Jeśli wszystko czego potrzebujesz to część napisu przed i po meczu, można użyć @- i @+ tablice dostać przesunięcia do dopasowane wyrażenie:

sub check { 
    my ($line, $regex) = @_; 
    if ($line =~ /$regex/) { 
     my $pre = substr $line, 0, $-[0]; 
     my $match = substr $line, $-[0], $+[0] - $-[0]; 
     my $post = substr $line, $+[0]; 
     print "<$pre><$match><$post>\n"; 
    } 
} 
1

Perl wersji> 5.22 jest zgłaszane jako modyfikator "/ n", który powoduje wyłączenie wszystkich przechwytywania.

Powiązane problemy