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

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

This is a sample 123 text and some 987 numbers

и шаблон:

/([\d]+)/

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

Ответ 1

Ключом к тому, чтобы заставить это работать, является указание 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/'

выводит "a a a a".

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

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

или вариации, такие как:

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

Параметр -P позволяет использовать регулярные выражения, совместимые с Perl. См. man 3 pcrepattern или man 3 pcresyntax.

Ответ 2

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

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

Ответ 3

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

grep -Eow "[0-9]+" file

Ответ 4

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

Если у вас есть 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 удалить набор и пробелы вокруг =.)

Ответ 5

Откажитесь и используйте 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
    

    С lookbehind:

    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
    

    С lookbehind:

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

    Вывод:

    1
    3478
    

Ответ 6

пробег цифр

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

$ 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' (ярлык). Аксиальный эквивалент, использованный выше [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) команды.

Ответ 7

Try

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
$

Ответ 8

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

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

Выдает следующее:

123
987