Витягнення регулярного вираза, сумісного з '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, як і всі інші інструменти 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' -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тексті заміни.) Це працює тому , що СЕД виглядає для самого раннього матчу регулярного виразу, і ми не будемо намагатися відповідати тому , що передує 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.