it-swarm-ru.tech

Может ли grep выводить только указанные группы, которые совпадают?

Скажи, у меня есть файл:

_# file: 'test.txt'
foobar bash 1
bash
foobar happy
foobar
_

Я только хочу знать, какие слова появляются после "foobar", поэтому я могу использовать это регулярное выражение:

_"foobar \(\w\+\)"
_

Скобки указывают, что у меня есть особый интерес к Слову сразу после foobar. Но когда я делаю grep "foobar \(\w\+\)" test.txt, я получаю целые строки, которые соответствуют всему регулярному выражению, а не просто "Слово после foobar":

_foobar bash 1
foobar happy
_

Я бы предпочел, чтобы выходные данные этой команды выглядели так:

_bash
happy
_

Есть ли способ сказать grep выводить только элементы, которые соответствуют группировке (или определенной группировке) в регулярном выражении?

338
Cory Klein

GNU grep имеет -P опция для регулярных выражений в стиле Perl и -o возможность печатать только то, что соответствует шаблону. Их можно объединить с помощью проверочных утверждений (описанных в разделе Расширенные шаблоны на man-странице perlre ), чтобы удалить часть шаблона grep из того, что определено как совпадающее для целей -o.

$ grep -oP 'foobar \K\w+' test.txt
bash
happy
$

\K - это краткая (и более эффективная) форма (?<=pattern), который вы используете в качестве скрытого утверждения нулевой ширины перед текстом, который вы хотите вывести. (?=pattern) может использоваться как упреждающее утверждение нулевой ширины после текста, который вы хотите вывести.

Например, если вы хотите сопоставить Word между foo и ​​bar, вы можете использовать:

$ grep -oP 'foo \K\w+(?= bar)' test.txt

или (для симметрии)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt
373
camh

Стандартный grep не может этого сделать, но последние версии GNU grep can . Вы можете обратиться к sed, awk или Perl. Вот несколько примеров, которые делают то, что Вы хотите, чтобы на вашем примере ввода, они ведут себя немного иначе в угловых случаях.

Заменить foobar Word other stuff by Word, печатать только после замены.

sed -n -e 's/^foobar \([[:alnum:]]\+\).*/\1/p'

Если первое слово - foobar, выведите второе слово.

awk '$1 == "foobar" {print $2}'

Уберите foobar, если это первое Слово, и пропустите строку в противном случае; затем удалите все после первого пробела и напечатайте.

Perl -lne 's/^foobar\s+// or next; s/\s.*//; print'
49
    sed -n "s/^.*foobar\s*\(\S*\).*$/\1/p"

-n     suppress printing
s      substitute
^.*    anything before foobar
foobar initial search match
\s*    any white space character (space)
\(     start capture group
\S*    capture any non-white space character (Word)
\)     end capture group
.*$    anything after the capture group
\1     substitute everything with the 1st capture group
p      print it
46
jgshawkey

Что ж, если вы знаете, что foobar всегда первое слово или строка, то вы можете использовать cut. Вот так:

grep "foobar" test.file | cut -d" " -f2
19
Dave

pcregrep умнее -o опция, которая позволяет вам выбрать, какие группы захвата вы хотите выводить. Итак, используя ваш файл примера,

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy
12

Если PCRE не поддерживается, вы можете достичь того же результата с помощью двух вызовов grep. Например, чтобы взять слово после foobar сделайте это:

<test.txt grep -o 'foobar  *[^ ]*' | grep -o '[^ ]*$'

Это может быть расширено до произвольного Word после foobar вот так (с ERE для удобства чтения):

i=1
<test.txt egrep -o 'foobar +([^ ]+ +){'$i'}[^ ]+' | grep -o '[^ ]*$'

Вывод:

1

Обратите внимание, что индекс i начинается с нуля.

9
Thor

Использование grep не является кроссплатформенным, поскольку -P/--Perl-regexp доступно только для GNU grep , но не BSD grep .

Вот решение, использующее ripgrep :

$ rg -o "foobar (\w+)" -r '$1' <test.txt
bash
happy

Согласно man rg:

-r/--replace REPLACEMENT_TEXT Заменить каждое совпадение приведенным текстом.

Индексы захвата группы (например, $5) и имена (например, $foo) поддерживаются в строке замены.

Похожие: GH-462 .

7
kenorb

Я нашел ответ @jgshawkey очень полезным. grep не очень хороший инструмент для этого, но sed есть, хотя здесь у нас есть пример, который использует grep для захвата соответствующей строки.

Синтаксис регулярных выражений sed уникален, если вы к нему не привыкли.

Вот еще один пример: этот анализирует выходные данные xinput, чтобы получить целое число идентификатора

⎜   ↳ SynPS/2 Synaptics TouchPad                id=19   [slave  pointer  (2)]

и я хочу 19

export TouchPadID=$(xinput | grep 'TouchPad' | sed  -n "s/^.*id=\([[:digit:]]\+\).*$/\1/p")

Обратите внимание на синтаксис класса:

[[:digit:]]

и необходимость избежать следующего +

Я предполагаю, что только одна строка соответствует.

2
Tim Richardson