web-dev-qa-db-ja.com

grepは、一致する指定されたグループのみを出力できますか?

ファイルがあるとしましょう:

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

「foobar」の後に表示される単語を知りたいだけなので、次の正規表現を使用できます。

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

括弧は、foobarの直後のWordに特別な関心があることを示しています。しかし、grep "foobar \(\w\+\)" test.txtを実行すると、「foobarの後のWord」だけでなく、正規表現全体と一致する行全体が取得されます。

_foobar bash 1
foobar happy
_

そのコマンドの出力が次のようになっていることを強く望みます。

_bash
happy
_

正規表現のグループ(または特定のグループ)に一致するアイテムのみを出力するようにgrepに指示する方法はありますか?

338
Cory Klein

GNU grepには、Perlスタイルの正規表現用の-Pオプションと、パターンに一致するものだけを出力する-oオプションがあります。これらは、アラウンドアサーション(perlreマンページの Extended Patterns で説明)を使用して組み合わせることができ、-oの目的で一致したと判断されたものからgrepパターンの一部を削除します。

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

\K(?<=pattern)の短い形式(およびより効率的な形式)であり、出力するテキストの前にゼロ幅後読みアサーションとして使用します。 (?=pattern)は、出力するテキストの後のゼロ幅先読みアサーションとして使用できます。

たとえば、foobarの間で単語を一致させたい場合は、次のように使用できます。

$ 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の場合、2番目の単語を印刷します。

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

最初の単語である場合はfoobarを取り除き、そうでない場合はその行をスキップします。次に、最初の空白の後のすべてを取り除いて印刷します。

Perl -lne 's/^foobar\s+// or next; s/\s.*//; print'
    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が常に最初のWordまたは行であることがわかっている場合は、cutを使用できます。そのようです:

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

pcregrepの方が賢い-oオプションで、出力するキャプチャグループを選択できます。したがって、サンプルファイルを使用して、

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

PCREがサポートされていない場合は、grepを2回呼び出すことで同じ結果を得ることができます。たとえばfoobarの後にWordを取得するには、次のようにします。

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

これは、次のようにfoobarの後に任意のWordに展開できます(読みやすくするためにEREを使用)。

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

出力:

1

インデックスiはゼロベースであることに注意してください。

9
Thor

grepの使用は、クロスプラットフォーム互換ではありません。-P/--Perl-regexpGNU 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の出力を解析してID整数を取得します

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

そして、私は19が欲しい

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

クラスの構文に注意してください。

[[:digit:]]

そして、次のエスケープする必要性+

1行だけが一致するとします。

2
Tim Richardson