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