Может ли Regex возвращать номер строки, в которой найдено совпадение?

В текстовом редакторе я хочу заменить данное слово номером строки, на котором это слово найдено. Возможно ли это с помощью Regex?

Ответ 1

Рекурсия, группа самореференций (трюк Qtax), обратные Qtax или балансировочные группы

Введение

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

Да, это возможно... С некоторыми оговорками и обманом регулярного выражения.

  • Решения в этом ответе означают как средство демонстрации синтаксиса регулярного выражения, а не практические ответы, которые должны быть реализованы.
  • В конце вашего файла мы вставим список номеров, которым предшествует уникальный разделитель. Для этого эксперимента добавленная строка :1:2:3:4:5:6:7 Это аналогичный метод известного хакера базы данных, который использует таблицу целых чисел.
  • Для первых двух решений нам нужен редактор, который использует аромат регулярного выражения, который позволяет рекурсию (решение 1) или группы саморегуляции захвата (решения 2 и 3). Два приходят на ум: Notepad ++ и EditPad Pro. Для третьего решения нам нужен редактор, который поддерживает балансировочные группы. Это, вероятно, ограничивает нас EditPad Pro или Visual Studio 2013 +.

Входной файл:

Скажем, мы ищем pig и хотим заменить его номером строки.

Мы будем использовать это как ввод:

my cat
dog
my pig
my cow
my mouse

:1:2:3:4:5:6:7

Первое решение: рекурсия

Поддерживаемые языки: помимо текстовых редакторов, упомянутых выше (Notepad ++ и EditPad Pro), это решение должно работать на языках, использующих PCRE (PHP, R, Delphi), в Perl и на Python с использованием модуля Matthew Barnett regex (непроверенные).

Рекурсивная структура живет в представлении и не является обязательной. Его задача состоит в том, чтобы сбалансировать строки, которые не содержат pig, слева, с цифрами справа: подумайте об этом как о балансировке вложенной конструкции, например {{{ }}}... За исключением того, что слева у нас нет -match lines, а справа мы имеем числа. Дело в том, что когда мы выходим из lookahead, мы знаем, сколько строк было пропущено.

Поиск:

(?sm)(?=.*?pig)(?=((?:^(?:(?!pig)[^\r\n])*(?:\r?\n))(?:(?1)|[^:]+)(:\d+))?).*?\Kpig(?=.*?(?(2)\2):(\d+))

Версия свободного пробела с комментариями:

(?xsm)             # free-spacing mode, multi-line
(?=.*?pig)        # fail right away if pig isn't there

(?=               # The Recursive Structure Lives In This Lookahead
(                 # Group 1
   (?:               # skip one line 
      ^              
      (?:(?!pig)[^\r\n])*  # zero or more chars not followed by pig
      (?:\r?\n)      # newline chars
    ) 
    (?:(?1)|[^:]+)   # recurse Group 1 OR match all chars that are not a :
    (:\d+)           # match digits
)?                 # End Group 
)                 # End lookahead. 
.*?\Kpig                # get to pig
(?=.*?(?(2)\2):(\d+))   # Lookahead: capture the next digits

Заменить: \3

В демо, см. подстановки внизу. Вы можете играть с буквами в первых двух строках (удалить пробел, чтобы сделать pig), чтобы перенести первое вхождение pig в другую строку и посмотреть, как это влияет на результаты.


Второе решение: группа, которая относится к самому себе ( "Qtax Trick" )

Поддерживаемые языки: помимо текстовых редакторов, упомянутых выше (Notepad ++ и EditPad Pro), это решение должно работать на языках, использующих PCRE (PHP, R, Delphi), в Perl и на Python с использованием модуля Matthew Barnett regex (непроверенные). Решение легко адаптируется к .NET, преобразовывая \K в lookahead и притяжательный квантификатор в атомную группу (см..NET Version несколько строк ниже.)

Поиск:

(?sm)(?=.*?pig)(?:(?:^(?:(?!pig)[^\r\n])*(?:\r?\n))(?=[^:]+((?(1)\1):\d+)))*+.*?\Kpig(?=[^:]+(?(1)\1):(\d+))

Версия .NET: Назад в будущее

.NET не имеет \K. Это его место, мы используем "назад к будущему" lookbehind (lookbehind, который содержит взгляд, который пропускает перед матчем). Кроме того, нам нужно использовать атомную группу вместо притяжательного квантификатора.

(?sm)(?<=(?=.*?pig)(?=(?>(?:^(?:(?!pig)[^\r\n])*(?:\r?\n))(?=[^:]+((?(1)\1):\d+)))*).*)pig(?=[^:]+(?(1)\1):(\d+))

Версия свободного пробела с комментариями (версия Perl/PCRE):

(?xsm)             # free-spacing mode, multi-line
(?=.*?pig)        # lookahead: if pig is not there, fail right away to save the effort
(?:               # start counter-line-skipper (lines that don't include pig)
   (?:               # skip one line 
      ^              # 
      (?:(?!pig)[^\r\n])*  # zero or more chars not followed by pig
      (?:\r?\n)      # newline chars
    )   
   # for each line skipped, let Group 1 match an ever increasing portion of the numbers string at the bottom
   (?=             # lookahead
      [^:]+           # skip all chars that are not colons
      (               # start Group 1
        (?(1)\1)      # match Group 1 if set
        :\d+          # match a colon and some digits
      )               # end Group 1
   )               # end lookahead
)*+               # end counter-line-skipper: zero or more times
.*?               # match
\K                # drop everything we've matched so far
pig               # match pig (this is the match!)
(?=[^:]+(?(1)\1):(\d+))   # capture the next number to Group 2

Заменить:

\2

Выход:

my cat
dog
my 3
my cow
my mouse

:1:2:3:4:5:6:7

В демо, см. подстановки внизу. Вы можете играть с буквами в первых двух строках (удалить пробел, чтобы сделать pig), чтобы перенести первое вхождение pig в другую строку и посмотреть, как это влияет на результаты.

Выбор разделителя для цифр

В нашем примере разделитель : для строки цифр довольно распространен и может произойти в другом месте. Мы можем придумать UNIQUE_DELIMITER и слегка подстроить выражение. Но следующая оптимизация еще более эффективна и позволяет нам хранить :


Оптимизация для второго решения: обратная строка цифр

Вместо того, чтобы вставлять наши цифры в порядок, нам может быть полезно использовать их в обратном порядке: :7:6:5:4:3:2:1

В наших взглядах это позволяет нам спуститься к нижней части ввода с помощью простого .* и начать отступать оттуда. Поскольку мы знаем, что мы находимся в конце строки, нам не нужно беспокоиться о том, что :digits является частью другого раздела строки. Вот как это сделать.

Ввод:

my cat pi g
dog p ig
my pig
my cow
my mouse

:7:6:5:4:3:2:1

Поиск:

(?xsm)             # free-spacing mode, multi-line
(?=.*?pig)        # lookahead: if pig is not there, fail right away to save the effort
(?:               # start counter-line-skipper (lines that don't include pig)
   (?:               # skip one line that doesn't have pig
      ^              # 
      (?:(?!pig)[^\r\n])*  # zero or more chars not followed by pig
      (?:\r?\n)      # newline chars
    )   
   # Group 1 matches increasing portion of the numbers string at the bottom
   (?=             # lookahead
      .*           # get to the end of the input
      (               # start Group 1
        :\d+          # match a colon and some digits
        (?(1)\1)      # match Group 1 if set
      )               # end Group 1
   )               # end lookahead
)*+               # end counter-line-skipper: zero or more times
.*?               # match
\K                # drop match so far
pig               # match pig (this is the match!)
(?=.*(\d+)(?(1)\1))   # capture the next number to Group 2

Заменить: \2

Смотрите подстановки в демо.

Третье решение: балансировочные группы

Это решение специфично для .NET.

Поиск:

(?m)(?<=\A(?<c>^(?:(?!pig)[^\r\n])*(?:\r?\n))*.*?)pig(?=[^:]+(?(c)(?<-c>:\d+)*):(\d+))

Версия свободного пробела с комментариями:

(?xm)                # free-spacing, multi-line
(?<=                 # lookbehind
   \A                # 
   (?<c>               # skip one line that doesn't have pig
                       # The length of Group c Captures will serve as a counter
     ^                    # beginning of line
     (?:(?!pig)[^\r\n])*  # zero or more chars not followed by pig
     (?:\r?\n)            # newline chars
   )                   # end skipper
   *                   # repeat skipper
   .*?                 # we're on the pig line: lazily match chars before pig
   )                # end lookbehind
pig                 # match pig: this is the match
(?=                 # lookahead
   [^:]+               # get to the digits
   (?(c)               # if Group c has been set
     (?<-c>:\d+)         # decrement c while we match a group of digits
     *                   # repeat: this will only repeat as long as the length of Group c captures > 0 
   )                   # end if Group c has been set
   :(\d+)              # Match the next digit group, capture the digits
)                    # end lokahead

Заменить: $1


Ссылка

Ответ 2

Поскольку вы не указали, какой текстовый редактор, в vim это будет:

:%s/searched_word/\=printf('%-4d', line('.'))/g (подробнее)

Но как кто-то упомянул об этом не вопрос для SO, а скорее Super User;)

Ответ 3

Я не знаю редактора, который не ограничивается расширением редактора, который позволяет произвольные расширения.

Вы можете легко использовать perl для выполнения задачи.

perl -i.bak -e"s/word/$./eg" file

Или, если вы хотите использовать подстановочные знаки,

perl -MFile::DosGlob=glob -i.bak -e"BEGIN { @ARGV = map glob($_), @ARGV } s/word/$./eg" *.txt