Извлечение регулярного выражения, совпадающего с 'sed' без печати окружающих символов

Для всех «sed» врачей:

Как вы можете получить «sed» для извлечения регулярного выражения, которое оно сопоставлено в строке?

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

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

regular expression to be isolated gets `inserted` here | v sed -n 's/.*\( \).*/\1/p 

это работает для некоторых выражений типа

  sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p 

который аккуратно извлекает все макрокоманды, начинающиеся с «CONFIG_ ….» (найденные в некотором «* .h» файле) и распечатывают все строки за строкой

  CONFIG_AT91_GPIO CONFIG_DRIVER_AT91EMAC . . CONFIG_USB_ATMEL CONFIG_USB_OHCI_NEW . etc 

НО приведенное выше разбивается на что-то вроде

  sed -n 's/.*\([0-9][0-9]*\).*/\1/p 

это всегда возвращает отдельные цифры, такие как

  7 9 . . 6 

вместо того, чтобы извлекать непрерывное числовое поле, такое как.

  8908078 89670890 . . . 23019 . etc 

PS: Я был бы признателен за отзывы о том, как это достигается в «sed». Я знаю, как это сделать с помощью «grep» и «awk». Я хотел бы узнать, имеет ли мое (хотя и ограниченное) понимание «sed» наличие дыр в нем, и если есть способ сделать это в «sed», который я
просто упустили из виду.

Когда регулярное выражение содержит группы, может быть несколько способов сопоставить строку с ним: регулярные выражения с группами неоднозначны. Например, рассмотрим regexp ^.*\([0-9][0-9]*\)$ и строку a12 . Есть две возможности:

  • Сопоставьте против .* И 2 против [0-9]* ; 1 соответствует [0-9] .
  • Сопоставьте a1 с .* И пустую строку с [0-9]* ; 2 соответствует [0-9] .

Sed, как и все другие инструменты regexp, применяет самое раннее правило совпадения: сначала он пытается сопоставить первую часть переменной длины с строкой, которая как можно дольше. Если он находит способ сопоставить остальную часть строки с остальной частью регулярного выражения, отлично. В противном случае sed пытается выполнить следующее самое длинное совпадение для первой части переменной длины и повторяет попытку.

Здесь совпадение с самой длинной строкой сначала равно a1 против .* , Поэтому группа соответствует только 2 . Если вы хотите, чтобы группа запускалась раньше, некоторые механизмы регулярного выражения позволяют вам делать .* Менее жадным, но sed не имеет такой функции. Поэтому вам нужно устранить двусмысленность с помощью некоторого дополнительного якоря. Укажите, что ведущий .* может заканчиваться цифрой, так что первая цифра группы является первым возможным совпадением.

  • Если группа цифр не может находиться в начале строки:

     sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' 
  • Если группа цифр может находиться в начале строки, а ваш sed поддерживает \? оператор для дополнительных деталей:

     sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p' 
  • Если группа цифр может находиться в начале строки, придерживаясь стандартных конструкций регулярных выражений:

     sed -n -e 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' -et -e 's/^\([0-9][0-9]*\).*/\1/p' 

Кстати, это то же самое самое раннее правило совпадения, которое делает [0-9]* соответствовать цифрам после первого, а не последующему .* .

Обратите внимание: если в строке есть несколько последовательностей цифр, ваша программа всегда будет извлекать последнюю последовательность цифр, опять же из-за самого раннего правила наибольшего соответствия, применяемого к исходному .* . Если вы хотите извлечь первую последовательность цифр, вам нужно указать, что то, что происходит раньше, – это последовательность цифр.

 sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p' 

В общем случае, чтобы извлечь первое совпадение регулярного выражения, вам нужно вычислить отрицание этого регулярного выражения. Хотя это всегда теоретически возможно, размер отрицания растет экспоненциально с размером регулярного выражения, которое вы отрицаете, поэтому это часто непрактично.

Рассмотрим другой пример:

 sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p' 

Этот пример фактически показывает ту же проблему, но вы не видите ее на типичных входах. Если вы hello CONFIG_FOO_CONFIG_BAR это hello CONFIG_FOO_CONFIG_BAR , тогда команда выше выведет CONFIG_BAR , а не CONFIG_FOO_CONFIG_BAR .

Есть способ распечатать первый матч с sed, но это немного сложно:

 sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -ep 

(Предполагая, что ваш sed поддерживает \n для обозначения новой строки в тексте замены s .) Это работает, потому что sed ищет самое раннее совпадение регулярного выражения, и мы не пытаемся сопоставить то, что предшествует бит CONFIG_… Поскольку в строке нет новой строки, мы можем использовать ее как временный маркер. Команда T говорит, что нужно отказаться, если предыдущая команда s не соответствовала.

Когда вы не можете понять, как сделать что-то в sed, обратитесь к awk. Следующая команда печатает самое раннее самое длинное совпадение регулярного выражения:

 awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}' 

И если вам хочется сохранить это просто, используйте Perl.

 perl -l -ne '/[0-9]+/ && print $&' # first match perl -l -ne '/^.*([0-9]+)/ && print $1' # last match 

В то время как не sed , одна из вещей, которые часто игнорируются для этого, – это grep -o , который, на мой взгляд, является лучшим инструментом для этой задачи.

Например, если вы хотите получить все параметры CONFIG_ из конфигурации ядра, вы должны использовать:

 # grep -Eo 'CONFIG_[A-Z0-9_]+' config CONFIG_64BIT CONFIG_X86_64 CONFIG_X86 CONFIG_INSTRUCTION_DECODER CONFIG_OUTPUT_FORMAT 

Если вы хотите получить непрерывные последовательности чисел:

 $ grep -Eo '[0-9]+' foo 
 sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D' 

… будет делать это без всякой суеты, хотя вам может понадобиться буквальная новая строка вместо n s в поле правой подстановки. И, кстати, вещь .*CONFIG будет работать, только если на линии будет только одно совпадение – в противном случае она всегда будет только последней.

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

Вы можете использовать ту же стратегию, чтобы получить [num] -е вхождение в строке. Например, если вы хотите напечатать соответствие CONFIG только в том случае, если оно было третьим в строке:

 sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D' 

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

Я полагаю, что для числа вещей это тоже будет работать:

 sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p 

… с тем же предостережением, что и раньше, о правом \n . Это было бы даже быстрее, чем первое, но не может применяться, как правило, очевидно.

Для элемента CONFIG вы можете использовать цикл P;...;D выше с вашим шаблоном или вы могли бы сделать:

 sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p' 

… который немного больше задействован и работает, правильно упорядочив опорный приоритет sed . Он также изолирует все совпадения CONFIG в строке за один раз – хотя и делает то же самое предположение, что и раньше, – что каждое соответствие CONFIG будет разделено хотя бы одним не-буквенно-цифровым символом. С GNU sed вы можете написать его:

 sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'