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