Regex для поиска (/ замены) нескольких экземпляров символа в строке - PullRequest
0 голосов
/ 17 октября 2018

У меня есть (возможно, очень простой) вопрос о том, как создать (perl) регулярное выражение, perl -pe 's///g;', которое будет находить / заменять несколько экземпляров данного символа / набора символов в указанной строке.Изначально я думал, что флаг g "global" сделает это, но я явно не понимаю чего-то очень важного.: /

Например, я хочу исключить любые не алфавитно-цифровые символы в определенной строке (в пределах большого текстового корпуса).В качестве примера, строка идентифицируется, начиная с [сопровождаемого @, возможно с некоторыми символами между ними.

[abc@def"ghi"jkl'123]

Следующее регулярное выражение

s/(\[[^\[\]]*?@[^\[\]]*?)[^a-zA-Z0-9]+?([^\[\]]*?)/$1$2/g;

найдет первый"и если я запускаю его три раза, у меня есть все три. Точно так же, что если я захочу заменить не алфавитно-цифровые символы чем-то другим, скажем, X.

s/(\[[^\[\]]*?@[^\[\]]*?)[^a-zA-Z0-9]+?([^\[\]]*?)/$1X$2/g; 

делает трюк для одного экземпляра"Но как мне найти их всех за один раз?

Ответы [ 3 ]

0 голосов
/ 17 октября 2018

Вот другой подход.Захватите именно подстроку, которая нуждается в работе, и в заменяющей части запустите регулярное выражение, очищающее ее от не алфавитно-цифровых символов

use warnings;
use strict;
use feature 'say';

my $var = q(ah [abc@def"ghi"jkl'123] oh); #'
say $var;

$var =~ s{ \[ [^\[\]]*? \@\K ([^\]]+) }{
    (my $v = $1) =~ s{[^0-9a-zA-Z]}{}g;
    $v
}ex;

say $var;

там, где необходим одиночный $v, чтобы вернуть его, а неколичество совпадений, которое возвращает сам оператор s/.Это можно улучшить с помощью модификатора /r, который возвращает измененную строку и не меняет оригинал (поэтому он не пытается изменить $1, что не разрешено)

$var =~ s{ \[ [^\[\]]*? \@\K ([^\]]+) }{
    $1 =~ s/[^0-9a-zA-Z]//gr;
}ex;

\K существует для того, чтобы все совпадения перед его «отбрасыванием» - они не потребляются, поэтому нам не нужно захватывать их, чтобы вернуть их обратно.Модификатор /e позволяет заменить заменяемую часть как код.

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

Проблема со совпадениями, которые мы хотим оставить в строке, часто может быть исправлена ​​с помощью \K (используется во всех текущих ответах), что делает так, чтобы всесовпадения до того, как не потреблено.

0 голосов
/ 17 октября 2018

Причина, по которой ваш код не работает, заключается в том, что /g не выполняет повторное сканирование строки после подстановки.Он находит все неперекрывающиеся совпадения данного регулярного выражения и затем заменяет замещающую часть в.

В [abc@def"ghi"jkl'123] существует только одно совпадение (которое является частью [abc@def" строки, с $1 = '[abc@def' и $2 = ''), поэтому удаляется только первый ".

После первого совпадения Perl сканирует оставшуюся строку (ghi"jkl'123]) для другого совпадения, но не находит другого[ (или @).


Я думаю, что наиболее простым решением является использование вложенной операции поиска / замены.Внешнее совпадение идентифицирует строку, в которой производится замена, а внутреннее сопоставление выполняет фактическую замену.

В коде:

s{ \[ [^\[\]\@]* \@ \K ([^\[\]]*) (?= \] ) }{ $1 =~ tr/a-zA-Z0-9//cdr }xe;

Или для замены каждого совпадения на X:

s{ \[ [^\[\]\@]* \@ \K ([^\[\]]*) (?= \] ) }{ $1 =~ tr/a-zA-Z0-9/X/cr }xe;

Мы сопоставляем префикс [, за которым следуют 0 или более символов, которые не являются [ или ] или @, за которыми следует @.

\K используется для обозначения виртуального начала совпадения (т. Е. Все сопоставленное до сих пор не включено в сопоставляемую строку, что упрощает подстановку).

Мы сопоставляем и фиксируем 0 или более символов, которые не [ или ].

Наконец, мы сопоставляем суффикс ] в прогнозе (так что он также не является частью совпадающей строки).

Замена выполняетсякак кусок кода, а не строка (как указано флагом /e).Здесь мы могли бы использовать $1 =~ s/[^a-zA-Z0-9]//gr или $1 =~ s/[^a-zA-Z0-9]/X/gr, соответственно, но поскольку каждое внутреннее совпадение является просто одним символом, также можно использовать транслитерацию.

Мы возвращаем измененную строку (как указано в/r) и использовать его в качестве замены во внешней операции s.

0 голосов
/ 17 октября 2018

Итак ... Я собираюсь предложить удивительно вычислительно неэффективный подход к этому.Удивительно неэффективно, но, возможно, все же быстрее, чем variable-length lookbehind будет ... и также легко (для вас):

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

perl -pe 'while (s/\[[^]]*@[^]]*\K[^]a-zA-Z0-9]//){}' file

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

Немного улучшенная версия:

perl -pe 'while (s/\[[^]]*?@[^]]*?\K[^]a-zA-Z0-9](?=[^]]*?])//){}' file

(?=) проверяет, что его содержимое существует после совпадения, не будучи частью совпадения.Это variable-length lookahead (то, что нам не хватает в другом направлении).Я также сделал * s ленивым с ?, чтобы мы получили максимально короткое совпадение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...