Коли регулярний вираз містить групи, може бути більше одного способу зіставити рядок проти нього: регулярні вирази з групами неоднозначні. Наприклад, розглянемо регулярне вираження ^.*\([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