| View previous topic :: View next topic |
| Author |
Message |
Batya

Joined: 15 Dec 2004 Posts: 2231 Location: Москва, Россия
|
(Separately) Posted: Thu Jul 16, 2026 19:41 Post subject: Примеры регулярных выражений |
|
|
Комрады, прошу помощи. Что-то мозг закипает.
Есть текст вида
| Quote: | _|_123_|_124_|_125_|_123_|_126_|_127
_|_123_|_128_|_129
|
Задача - если в тексте встречается такой же текст, как в начале строки по шаблону "^(_\|_\d+_\|)", заменить на перенос строки такой текст в любом месте строки. Т. е. применительно к указанному примеру заменять нужно исключительно "_|_123_|", и должно получиться:
| Quote: |
_124_|_125
_126_|_127
_128_|_129
|
Заранее число, стоящее в начале строки неизвестно. Но заменять надо только блоки, содержащие число из начала строки.
Соответственно, аналогичный результат должен получиться и, например, для текста:
| Quote: | _|_78_|_124_|_125_|_78_|_126_|_127
_|_78_|_128_|_129
|
Если принципиально, использую поиск в Notepad++. _________________ Нет, я не сплю. Я просто медленно моргаю. |
|
| Back to top |
|
 |
Orion9

Joined: 01 Jan 2024 Posts: 1216
|
(Separately) Posted: Fri Jul 17, 2026 12:47 Post subject: |
|
|
| Batya wrote: | | Что-то мозг закипает |
Мозг действительно закипает
| Batya wrote: | | Если принципиально, использую |
А принципально использовать регулярные выражения? Может быть, для данной задачи лучше использовать скрипт?
Я сам никогда не пользовался, но есть, вроде, такие продвинутые вещи, как проверки и заглядывания вперед и назад (assertions: lookahead, lookahead), может быть, они подойдут для решения этой задачи? |
|
| Back to top |
|
 |
Batya

Joined: 15 Dec 2004 Posts: 2231 Location: Москва, Россия
|
(Separately) Posted: Fri Jul 17, 2026 13:08 Post subject: |
|
|
На работе используется Notepad++. Необходимо оперативно по определённым правилам менять текст в несохранённом документе. Для этого хочу записать Notepad++-макрос. Вот для записи макроса и пытаюсь правильно составить регулярное выражение поиска и замены.
Ключевые моменты - текст, как я говорил, не сохранён в виде файла; буфер обмена при использовании макроса трогать крайне нежелательно.
Ну и для себя пытаюсь разобраться в ранее неосвоенных тонкостях регулярных выражений. _________________ Нет, я не сплю. Я просто медленно моргаю. |
|
| Back to top |
|
 |
Orion9

Joined: 01 Jan 2024 Posts: 1216
|
(Separately) Posted: Fri Jul 17, 2026 13:26 Post subject: |
|
|
Batya
Я понял.
Почему бы не подключить к этому делу ИИ? Он меня уже не раз выручал. Главное держать его в узде вопросы правильные к нему формулировать  |
|
| Back to top |
|
 |
yozhik

Joined: 04 May 2014 Posts: 343 Location: Электросталь
|
(Separately) Posted: Fri Jul 17, 2026 20:16 Post subject: |
|
|
Batya
Обновлено: Прошу прощения, не вчитался! Корректный ответ ниже.
Сейчас нет возможности опробовать (Notepad++ не пользуюсь), но судя по справке должно работать вот это:
| Code: | Найти:
(?m-s)^(_\|_\d+_\|)(.+?\r\n)\1
Заменить:
\2 |
Если в файле окончания строк LF, тогда в поиске \r\n заменить на \n.
Попробуйте, пожалуйста, если не сработает, то скачаю портабельный Notepad++, погоняю. _________________ Amo ergo sum
Last edited by yozhik on Sat Jul 18, 2026 15:29; edited 1 time in total |
|
| Back to top |
|
 |
Orion9

Joined: 01 Jan 2024 Posts: 1216
|
(Separately) Posted: Sat Jul 18, 2026 00:42 Post subject: |
|
|
yozhik
А где пояснения в стиле ИИ?
Кстати, он мне говорит, что за одну итерацию невозможно заменить все \1. Только программным путем  |
|
| Back to top |
|
 |
yozhik

Joined: 04 May 2014 Posts: 343 Location: Электросталь
|
(Separately) Posted: Sat Jul 18, 2026 04:20 Post subject: |
|
|
| Orion9 wrote: | | А где пояснения в стиле ИИ? |
Это для молодых. Бате не надо, он насквозь видит.
| Orion9 wrote: | | он мне говорит, что за одну итерацию невозможно заменить все \1. |
Это он так денег просит. Культурно намекает.
Регулярка то рабочая, в EditPad Pro исправно отрабатывает:
Но каждый редактор надо отдельно проверять. В AkelPad, например, backreferenc'ы в строке поиска не всегда срабатывают. _________________ Amo ergo sum |
|
| Back to top |
|
 |
Orion9

Joined: 01 Jan 2024 Posts: 1216
|
(Separately) Posted: Sat Jul 18, 2026 11:59 Post subject: |
|
|
| yozhik wrote: | | Это для молодых. Бате не надо, он насквозь видит. |
Не беда. Как вы знаете, болвана ИИ всегда можно рубануть в лоб прямым вопросом:
 Hidden text объясни выражение (?m-s)^(_\|_\d+_\|)(.+?\r\n)\1
Это регулярное выражение предназначено для поиска двух идущих подряд строк, которые начинаются с одного и того же префикса-идентификатора. При этом первая строка полностью захватывается, а от второй строки захватывается только этот повторяющийся префикс.
Давайте разберем выражение по деталям:
## 1. Флаги и модификаторы (?m-s)
* ?m (Multiline) — включает многострочный режим. Символ ^ теперь означает начало каждой отдельной строки в тексте, а не начало всего документа.
* -s (no Singleline) — отключает режим, при котором точка совпадает с переносом строки. Здесь точка . означает любой символ, кроме переноса строки.
## 2. Поиск префикса ^(_\|_\d+_\|)
* ^ — ищет строго в начале строки.
* (...) — это первая захватывающая группа (Group 1). Текст, который сюда попадет, движок regex запомнит.
* _\|_ — ищет символы _|_. Символ вертикальной черты | экранирован обратным слэшем \|, чтобы он воспринимался как обычный текст, а не как логическое «ИЛИ».
* \d+ — ищет одну или более цифр подряд (например, 1, 42, 2026).
* _\| — ищет символы _|.
* Итог группы 1: Ищется маркер вида _|_42_| в самом начале строки.
## 3. Захват остатка первой строки (.+?\r\n)
* (...) — это вторая захватывающая группа (Group 2).
* .+? — ленивый поиск любых символов до конца текущей строки.
* \r\n — символ перевода строки (Windows-формат).
* Итог группы 2: Сюда сохраняется всё содержимое первой строки (вместе с её финальным переносом), кроме начального префикса.
## 4. Проверка дубликата \1
* \1 — это обратная ссылка (backreference) на Группу 1. Движок требует, чтобы в этом месте текста (то есть в начале следующей строки) находились ровно те же самые символы, которые были найдены в пункте 2.
------------------------------
## 📝 Визуальный пример работы
Допустим, у нас есть такой текст:
_|_105_| Текст первой строки.
_|_105_| Текст второй строки.
_|_200_| Другой текст.
Это регулярное выражение совпадет со следующим фрагментом:
1. ^(_\|_\d+_\|) найдет _|_105_| на первой строке и запомнит как \1.
2. (.+?\r\n) найдет Текст первой строки.[разрыв строки] и запомнит как \2.
3. \1 перейдет на вторую строку и проверит, начинается ли она с _|_105_|. Так как это совпадает, весь этот блок будет выделен.
Если вы будете использовать этот шаблон для замены (например, в AkelPad), вы можете заменить найденное на $1$2 (или \1\2), чтобы удалить префикс-дубликат со второй строки, объединив или очистив текст.
| yozhik wrote: | | Это он так денег просит. Культурно намекает. |
Да нет. Видимо, это я неправильно задачу понял. Я думал, что нужно искать и заменять в каждой строке отдельно, т.е. если есть текст из трех строк:
| Code: | _|_78_|_124_|_125_|_78_|_126_|_127
_|_78_|_128_|_129
_|_122_|_123_|_124_|_122_|_127_|_128 |
То буду заменены только строки 1 и 3, т.к. они содержат блоки с которого они начинаются. А если нужно заменить во всем тексте блоки с которого начинается этот текст, то не понимаю, зачем вообще регулярные выражения. Можно просто выделить начало текста и заменить. Или там сотни замен нужно делать каждый день? |
|
| Back to top |
|
 |
yozhik

Joined: 04 May 2014 Posts: 343 Location: Электросталь
|
(Separately) Posted: Sat Jul 18, 2026 15:26 Post subject: |
|
|
| Orion9 wrote: | | Видимо, это я неправильно задачу понял. |
Нет, это я её неправильно понял, точнее поторопился и просто не вчитался.
Ограничение регулярок в том, что для замены нужно знать точное кол-во совпадений. Т.е. я могу найти неопределённое число совпадений, но если мне нужно каждое из них заменить, то в замене я должен сослаться на каждое совпадение по его номеру, иначе никак.
Поэтому к Бате вопрос: этот шаблон стабильный или варьируется?
| Code: | _|_123_|_124_|_125_|_123_|_126_|_127
_|_123_|_128_|_129
другими словами
(искомое) ... (искомое) ...
(искомое) ... |
Т.е. «искомое» всегда ли 2жды в первой строке и 1жды во второй? Если да, то поможет вот это:
| Code: | Найти:
(?m-s)^(_\|_\d+_\|)(.+?)\1(.+?\r\n)\1
Заменить:
\r\n\2\r\n\3\r\n |
Если нет, то решение — перечислить в макросе по очереди все возможные варианты. Например:
| Code: | Для:
(искомое) ... (искомое) ... (искомое) ...
(искомое) ... (искомое) ...
Найти/Заменить:
(?m-s)^(_\|_\d+_\|)(.+?)\1(.+?)\1(.+?\r\n)\1(.+?)\1
\r\n\2\r\n\3\r\n\4\r\n\5\r\n
Для:
(искомое) ... (искомое) ... (искомое) ...
(искомое) ...
Найти/Заменить:
(?m-s)^(_\|_\d+_\|)(.+?)\1(.+?)\1(.+?\r\n)\1
\r\n\2\r\n\3\r\n\4\r\n
Для:
(искомое) ... (искомое) ...
(искомое) ...
Найти/Заменить:
(?m-s)^(_\|_\d+_\|)(.+?)\1(.+?\r\n)\1
\r\n\2\r\n\3\r\n
Для:
(искомое) ...
(искомое) ...
Найти/Заменить:
(?m-s)^(_\|_\d+_\|)(.+?\r\n)\1
\r\n\2\r\n
и т.п. |
Ну, или скрипт писать, да, программно. _________________ Amo ergo sum |
|
| Back to top |
|
 |
Orion9

Joined: 01 Jan 2024 Posts: 1216
|
(Separately) Posted: Sun Jul 19, 2026 11:59 Post subject: |
|
|
| yozhik wrote: | | Поэтому к Бате вопрос: |
Согласен, нужно уточнить задачу. Но раз уж Batya озаглавил тему так, что можно делиться собственным неудачным (или удачным?) опытом, то помню, когда сталкнулся в справке с такими примерами:
| Code: | RegEx Находит
foob.*r foobar, foobalkjdflkj9r и foobr
foob.+r foobar, foobalkjdflkj9r, но не foobr
foob.?r foobar, foobbr и foobr, но не foobalkj9r
fooba{2}r foobaar
fooba{2}r ` foobaar, foobaaar, foobaaaar` и т. д.
fooba{2,3}r foobaar, или foobaaar, но не foobaaaar
(foobar){8,10} 8, 9 или 10 экземпляров foobar (() это Группа) |
Очень мне не понравился этот fooba{2}r `. Хотелось бы услышать пояснений, но не догадался тогда у болвана спросить
 почему находит foobaar, foobaaar, foobaaaar` Если ваше регулярное выражение fooba{2}r находит слова с тремя, четырьмя или более буквами «а» (foobaaar, foobaaaar), это происходит по одной из двух причин:
## 1. Отсутствие строгих границ слова (Самая частая причина)
Движок регулярных выражений ищет частичное совпадение внутри строки. Квантификатор {2} требует, чтобы букв «а» было минимум две.
Когда движок видит слово foobaaar:
* Он проверяет префикс foob.
* Затем он видит группу aa (первые две буквы «а») — это условие a{2} выполнено.
* Но третья буква «а» для него становится частью следующего символа. Однако в вашем шаблоне дальше идёт буква r, а не a. Движок спотыкается.
* Тогда он включает механизм бэктрекинга (отката): он сдвигает своё окно поиска на одну букву вправо. Он берёт вторую и третью буквы «а» как выполнение условия a{2}, а за ними как раз идёт нужная буква r .
* В итоге внутри слова foob[aaa]r он успешно выделяет подстроку foob[aar] .
## 2. Специфика старого движка AkelPad
Если вы тестируете это в AkelPad, то его встроенный (старый стандартный) движок регулярных выражений имеет упрощенный синтаксис. В некоторых его версиях квантификатор {n} работает не как «строго n раз», а как «n или более раз» (то есть аналогично {n,}).
------------------------------
## 🛠️ Как это исправить?
Чтобы заставить движок искать строго две буквы «а» и игнорировать слова, где их больше, используйте один из вариантов:
1. Добавить негативную проверку (Lookbehind/Lookahead):
Это запретит буквам «а» стоять до или после нашей пары.
fo(?<!a)ba{2}(?!a)r `
2. Для AkelPad (через явное указание не-буквы):
Если вы ищете слова целиком, ограничьте их метасимволом границы слова \b:
\bfooba{2}r\b `
Но учтите, если в движке AkelPad баг с квантификатором, то для строго двух «а» надежнее написать их вручную без фигурных скобок: \bfoobaar\b |
|
| Back to top |
|
 |
yozhik

Joined: 04 May 2014 Posts: 343 Location: Электросталь
|
(Separately) Posted: Sun Jul 19, 2026 12:39 Post subject: |
|
|
| Orion9 wrote: | | fooba{2}r ` |
Ох, так ведь это ж очепятка У Сорокина такое встречается, к сожалению. Надо смотреть исходники, и в них сразу видны сбои автоматики:
https://github.com/andgineer/TRegExpr/blob/master/docs/src/ru/regular_expressions.md?plain=1#L332
Здесь должно было быть fooba{2,}r . Запятая после двойки у Сорокина потерялась, но сохранилась, например, здесь и здесь (поиск на странице по слову "foobalkjdflkj9r", так быстрее).
| болван wrote: | Однако в вашем шаблоне дальше идёт буква r, а не a. Движок спотыкается. ...
Он берёт вторую и третью буквы «а» как выполнение условия a{2}, а за ними как раз идёт нужная буква r . |
А болван то юморист! Хорошо насмешил. Занимательные такие антропоморфизмы. И большой плюс болвана в том, что когда вокруг смеются, ему не обидно  _________________ Amo ergo sum |
|
| Back to top |
|
 |
Orion9

Joined: 01 Jan 2024 Posts: 1216
|
(Separately) Posted: Sun Jul 19, 2026 14:14 Post subject: |
|
|
| yozhik wrote: | | Ох, так ведь это ж очепятка |
У него-то, может, опечатка. А меня картина мира перевернулась. Две ночи не мог уснуть, всё думал над дилеммой.
| yozhik wrote: | | У Сорокина такое встречается, к сожалению. Надо смотреть исходники, и в них сразу видны сбои автоматики: |
Смотреть исходники - это последнее, что бы мне пришло в голову
| yozhik wrote: | | А болван то юморист! |
Не только. Он еще и друг человека... И человек - бовану друг  |
|
| Back to top |
|
 |
Orion9

Joined: 01 Jan 2024 Posts: 1216
|
(Separately) Posted: Mon Jul 20, 2026 00:34 Post subject: |
|
|
yozhik
Чуть не забыл сказать спасибо за разъяснение по опечатке
А болван, да — радует своим креативом. Недавно задавался смежным вопросом, так почему бы его еще не помучать?
 Hidden text как оптимизировать и ускорить выражение (?i)(button|cmd|param|path|menu|iconic)(\d+?)=(.*?)(\R|\Z)
Для оптимизации этого выражения нужно устранить избыточный бэктрекинг (откаты движка), который вызывают ленивые квантификаторы (+? и *?), стоящие перед перебором вариантов и концом строки.
Вот максимально ускоренная и очищенная версия выражения:
(?i)\b(button|cmd|param|path|menu|iconic)(\d++)=(.*)
## 🛠 Что конкретно изменено и почему это увеличит скорость:
1. Замена (\R|\Z) на автоматический конец строки:
Вам не нужно вручную перебирать символ переноса \R или конец текста \Z. По умолчанию точка . (в обычном режиме, без флага s) не совпадает с переносом строки. Поэтому выражение (.*) само дойдет строго до конца текущей строки (или конца файла), работая в разы быстрее.
2. Замена ленивого *? на жадный .*:
Ленивый квантификатор (.*?) заставлял движок после каждого считанного символа проверять: «А не наступил ли еще перенос строки?». Жадный (.*) сразу забирает весь остаток строки до конца без лишних проверок.
3. Сверхжадный (обладающий) квантификатор \d++:
Знак ++ запрещает движку возвращаться и «отдавать» цифры назад, если дальнейшая часть выражения не совпала. Это на корню отсекает бэктрекинг на неудачных совпадениях.
4. Добавление границы слова \b:
Движок не будет пытаться проверить список слов (button, cmd...) внутри других длинных слов (например, в слове com**mand**). Поиск начнется только с начала слова.
------------------------------
## 📊 Сравнение структуры (было / стало)
| Code: | | Параметр | Исходное выражение | Оптимизированное выражение |
|---|---|---|
| Поведение группы цифр | Ленивое (\d+?) — медленный пошаговый перебор | Сверхжадное (\d++) — мгновенный захват |
| Поведение значения | Ленивое ((.*?)) — постоянные спотыкания | Жадное ((.*)) — линейное чтение до упора |
| Проверка конца строки | Тяжелая группа альтернатив (\R|\Z) | Естественная граница точки . |
|
сможет ли оптимизированное выражение обработать многострочный файл в TRegExpr
Нет, в исходном виде эта оптимизированная строка вызовет ошибку синтаксиса или отработает некорректно в библиотеке TRegExpr. [1]
Дело в том, что TRegExpr (популярная библиотека для Delphi и Lazarus) имеет упрощенный, классический Perl-подобный синтаксис. В ней отсутствует поддержка сверхжадных (possessive) квантификаторов (таких как ++). [2, 3, 4]
Чтобы выражение гарантированно и быстро обрабатывало многострочный текст в TRegExpr, его нужно адаптировать под этот движок.
## 🛠️ Адаптированная версия для TRegExpr:
(?mi)^(button|cmd|param|path|menu|iconic)(\d+)=(.*)
## 💡 Что учтено для правильной работы в многострочном режиме:
1. Добавлен флаг многострочности (?mi)
Флаг m критически важен. Без него метасимвол начала строки ^ проверял бы только самое начало файла, пропуская все остальные строки. [5]
2. Добавлен якорь начала строки ^
Поскольку мы убрали \b, символ ^ жестко связывает поиск с началом каждой строки. Движок мгновенно отбросит строки, которые начинаются не с ключевых слов. [5]
3. Исправлен квантификатор \d+
Так как синтаксис \d++ библиотека TRegExpr не поймет, мы возвращаем стандартный жадный \d+. В связке с символом = он отработает максимально быстро и без лишних откатов. [2]
4. Безопасный жадный захват (.*)
В TRegExpr точка . по умолчанию не включает в себя символ перевода строки \n. Поэтому жадный квантификатор (.*) дойдет строго до конца текущей строки и остановится, не перескакивая на следующую.
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|