Может ли 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 выводить элементы, которые соответствуют группировке (или определенной группе) в регулярном выражении?

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

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

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

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

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

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

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

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

Замените foobar word other stuff 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' 

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

 grep "foobar" test.file | cut -d" " -f2 
  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 

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

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

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

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

Вывод:

 1 

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