Как вывести только захваченные группы с помощью sed?

278

Есть ли способ сказать, sedчтобы вывести только захваченные группы? Например, с учетом ввода:

This is a sample 123 text and some 987 numbers

и шаблон:

/([\d]+)/

Могу ли я получить только 123 и 987 вывод способом, отформатированным обратными ссылками?

Pablo
источник
Обратите внимание, что для захвата группы необходимо sedвключить расширенные регулярные выражения с -Eфлагом.
Петер - Восстановить Монику

Ответы:

333

Ключом к тому, чтобы заставить это работать, является sedуказание исключить то, что вы не хотите выводить, а также указать, что вы хотите.

string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Это говорит:

  • не по умолчанию печатать каждую строку ( -n)
  • исключить ноль или более не цифр
  • включать одну или несколько цифр
  • исключить одну или несколько не цифр
  • включать одну или несколько цифр
  • исключить ноль или более не цифр
  • напечатать замещение ( p)

В общем, sedвы захватываете группы, используя скобки, и выводите то, что вы захватываете, используя обратную ссылку:

echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'

выведет "бар". Если вы используете -r( -Eдля OS X) для расширенного регулярного выражения, вам не нужно экранировать скобки:

echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'

Может быть до 9 групп захвата и их обратных ссылок. Обратные ссылки нумеруются в порядке появления групп, но их можно использовать в любом порядке и повторять:

echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'

выводит "бар а".

Если у вас есть GNU grep(он также может работать в BSD, включая OS X):

echo "$string" | grep -Po '\d+'

или варианты, такие как:

echo "$string" | grep -Po '(?<=\D )(\d+)'

-PОпция позволяет Perl Compatible Regular Expressions. Смотрите man 3 pcrepatternили man 3 pcresyntax.

Приостановлено до дальнейшего уведомления.
источник
24
Как примечание, OSX Mountain Lion больше не поддерживает PCRE в grep.
Инкраш
1
Как примечание: опция grep -o не поддерживается в Solaris 9. Кроме того, Solaris 9 не поддерживает параметр sed -r. :(
Даниэль Кац
7
Попросите вашего системного администратора установить gsed. Вы будете удивлены тем, что несколько пончиков получат вас ...
avgvstvs
3
Обратите внимание, что вам может понадобиться поставить префикс «(» и «)» на «\», я не знаю почему.
lumbric
7
@lumbric: если вы ссылаетесь на sedпример, если вы используете -rопцию (или -Eдля OS X, IIRC), вам не нужно избегать скобок. Разница заключается в том, что между базовыми регулярными выражениями и расширенными регулярными выражениями ( -r).
Приостановлено до дальнейшего уведомления.
55

Sed имеет до девяти запоминаемых шаблонов, но вам нужно использовать экранированные скобки, чтобы запомнить части регулярного выражения.

Смотрите здесь для примеров и более подробно

Питер МакГ
источник
58
sed -e 's/version=\(.+\)/\1/' input.txtэто все равно будет выводить весь input.txt
Пабло
@Pablo, по твоему шаблону надо писать \+вместо +. И я не понимаю, почему люди используют -eтолько одну команду sed.
Фредрик Гаусс
1
использование sed -e -n 's/version=\(.+\)/\1/p' input.txtсм: mikeplate.com/2012/05/09/...
awattar
1
Я бы предложил sed -Eиспользовать так называемые «современные» или «расширенные» регулярные выражения, которые намного ближе к Perl / Java / JavaScript / Go / любым другим разновидностям. (Сравните с grep -Eили egrep.) Синтаксис по умолчанию имеет те странные экранирующие правила и считается "устаревшим". Для получения дополнительной информации о различиях между ними, запустите man 7 re_format.
AndrewF
31

вы можете использовать grep

grep -Eow "[0-9]+" file
ghostdog74
источник
4
@ ghostdog74: Абсолютно с тобой согласен. Как я могу получить Greo для вывода только захваченных групп?
Пабло
1
@Michael - вот почему oопция есть - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, --only-Match Показать только часть совпадающей строки, которая соответствует PATTERN
Bert F
14
@Bert F: Я понимаю подходящую часть, но это не группа захвата. Я хочу, чтобы это было так ([0-9] +). + ([Abc] {2,3}), чтобы было 2 группы захвата. Я хочу выводить ТОЛЬКО записи групп по обратным ссылкам или как-то еще.
Пабло
Привет Майкл. Вам удалось извлечь n-ю захваченную группу с помощью grep?
doc_id
1
@Pablo: grep выводит только то, что соответствует. Чтобы дать ему несколько групп, используйте несколько выражений: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"я не знаю, как вы могли бы требовать, чтобы эти два выражения находились на одной строке, помимо конвейера из предыдущего grep (который все равно может не работать, если какой-либо шаблон соответствует более одного раза в строке ).
idbrii
13

пробег (ы) цифр

Этот ответ работает с любым количеством цифр групп. Пример:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Расширенный ответ.

Есть ли способ сказать, что sed выводит только захваченные группы?

Да. замените весь текст группой захвата:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

Или с расширенным синтаксисом (меньше обратных кавычек и допускается использование +):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

Чтобы избежать печати оригинального текста, когда номера нет, используйте:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Не печатать ввод по умолчанию.
  • (/ p) печатать только если замена была сделана.

И сопоставить несколько чисел (а также распечатать их):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

Это работает для любого количества запусков цифр:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Что очень похоже на команду grep:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

О \ d

и шаблон: /([\d]+)/

Sed не распознает синтаксис «\ d» (ярлык). Используемый выше эквивалент ascii [0-9]не совсем эквивалентен. Единственное альтернативное решение - использовать класс символов: '[[: digit:]] `.

Выбранный ответ использует такие «классы символов» для построения решения:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Это решение работает только для (точно) двух серий цифр.

Конечно, поскольку ответ выполняется внутри оболочки, мы можем определить пару переменных, чтобы сделать такой ответ короче:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

Но, как уже было объяснено, s/…/…/gpлучше использовать команду:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

Это будет охватывать как повторные серии цифр, так и написание короткой (er) команды.

Исаак
источник
Удивленный после прочтения принятого ответа с высоким рейтингом, я прокрутил страницу вниз, чтобы написать о ее узком охвате и фактически затронуть суть вопроса. Я должен был догадаться, что кто-то сделал бы это уже много лет назад. Это очень хорошо объяснено и является правильным ответом.
Амит Найду
9

Я полагаю, что шаблон, приведенный в вопросе, был только в качестве примера, и цель состояла в том, чтобы соответствовать любому шаблону.

Если у вас есть sed с расширением GNU, позволяющим вставлять новую строку в пространство шаблонов, одно из предложений:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

Эти примеры с tcsh (да, я знаю, что это не та оболочка) с CYGWIN. (Правка: для bash удалите set и пробелы вокруг =.)

Джозеф Куинси
источник
@Joseph: спасибо, однако, исходя из моей задачи, я чувствую, что grep более естественен, как предположил ghostdog74. Просто нужно выяснить, как заставить grep выводить только группы захвата, а не все совпадения.
Пабло
2
Просто примечание, но знак «плюс» означает «один или несколько», что устранит необходимость повторения в шаблонах. Таким образом, «[0-9] [0-9] *» станет «[0-9] +»
RandomInsano
4
@RandomInsano: Для того, чтобы использовать его +, вам нужно его отключить или использовать -rопцию ( -Eдля OS X). Вы также можете использовать \{1,\}( -rили -Eбез побега).
Приостановлено до дальнейшего уведомления.
9

Сдайся и используй Perl

Так sedкак не обрезает его, давайте просто выбросим полотенце и используем Perl, по крайней мере это LSB, а grepрасширения GNU - нет :-)

  • Напечатайте всю соответствующую часть, не требуя соответствующих групп или вида сзади:

    cat <<EOS | perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS

    Вывод:

    12
    3456
  • Одно совпадение на строку, часто структурированные поля данных:

    cat <<EOS | perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS

    Вывод:

    1
    34

    Сзади

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • Несколько полей:

    cat <<EOS | perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS

    Вывод:

    1 2
    34 56
  • Несколько совпадений в строке, часто неструктурированные данные:

    cat <<EOS | perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Вывод:

    1 
    34 78

    Сзади

    cat EOS<< | perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Вывод:

    1
    3478
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
1
Что вы не получили в конце вопроса: «с седом»?
Moonchild
@Moonchild Googlers не волнует.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
1
Я нашел это полезным. не все проблемы регулярных выражений командной строки нужно решать с помощью sed.
PPPaul
5

Пытаться

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

Я получил это под Cygwin:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$
Берт F
источник
2

Это не то, о чем просил ФП (захват групп), но вы можете извлечь числа, используя:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

Дает следующее:

123
987
Томас Братт
источник