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


24

Для всех врачей «Сед» там:

Как вы можете получить '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
                   .
                 e.t.c. 

НО вышеперечисленное ломает что-то вроде

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

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

                 7
                 9
                 .
                 .  
                 6

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

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

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

Ответы:


22

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

  • Матч aпротив .*и 2против [0-9]*; 1соответствует [0-9].
  • Матч a1против .*и пустая строка против [0-9]*; 2соответствует [0-9].

Sed, как и все другие инструменты регулярного выражения, применяет самое раннее правило наибольшего совпадения: сначала он пытается сопоставить первую часть переменной длины со строкой, которая является максимально длинной. Если он находит способ сопоставить остальную часть строки с остальной частью регулярного выражения, хорошо. В противном случае 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' -e t -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, то вышеприведенная команда выводит CONFIG_BAR, а не CONFIG_FOO_CONFIG_BAR.

Есть способ напечатать первое совпадение с помощью sed, но это немного сложно:

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

(Предполагая, что ваш 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

22

Хотя это не так 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

7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

... сделает это без всякой суеты, хотя вам может понадобиться буквальный перевод строки вместо ns в правом поле замены. И, кстати, эта .*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'
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.