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


291

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

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

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

"foobar \(\w\+\)"

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

foobar bash 1
foobar happy

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

bash
happy

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


4
grepのを必要としない人のために:perl -lne 'print $1 if /foobar (\w+)/' < test.txt
ボールト

回答:


326

GNU grepには、-Pperlスタイルの正規表現の-oオプションと、パターンに一致するもののみを出力するオプションがあります。ルックアラウンドアサーション(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

3
正規表現にグループ化以上のものがある場合はどうしますか?(タイトルが暗示しているように?)
barracel

4
@barracel:できるとは思わない。の時間sed(1)
camh

1
@camh grep -oP 'foobar \K\w+' test.txtOPで何も出力しないことをテストしましたtest.txt。grepバージョンは2.5.1です。何が悪いのでしょうか?O_O
SOUser

@XichenLi:言えない。私はちょうどgrepのv2.5.1を作成しました(2006年からかなり古くなっています)。
カム14

@SOUser:同じことを経験しました-ファイルに何も出力しません。ファイル名の前に「>」を含めるように編集要求を送信して、出力を送信しました。
rjchicago 16

39

標準のgrepではこれができませんが、GNU grepの最近のバージョンではできます。sed、awk、またはperlを使用できます。サンプル入力で必要なことを行ういくつかの例を次に示します。これらは、コーナーケースではわずかに異なる動作をします。

交換するfoobar word other stuffことによりword、交換が行われた場合にのみ印刷します。

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

最初の単語がの場合、foobar2番目の単語を出力します。

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

ストリップfoobarそれは最初の単語だ場合は、それ以外の行をスキップします。その後、最初の空白の後にすべてを取り除き、印刷します。

perl -lne 's/^foobar\s+// or next; s/\s.*//; print'

驚くばかり!私はsedでこれを行うことができるかもしれないと思っていましたが、以前は使用していなかったので、使い慣れgrepたを使用できることを望んでいました。しかし、これらのコマンドの構文は、vimスタイルの検索と置換+正規表現に精通しているため、実際には非常に馴染みがあります。トンありがとう。
コリークライン

1
真実ではない、ジル。GNU grepソリューションについての私の答えをご覧ください。
カム

1
@camh:ああ、GNU grepがPCREを完全にサポートしていることを知りませんでした。回答を修正しました、ありがとう。
ジル

1
Busybox grepはPCREをサポートしていないため、この回答は組み込みLinuxで特に役立ちます。
クレイグマックイーン

明らかに、提示された同じタスクを達成するための複数の方法がありますが、OPがgrepの使用を要求する場合、なぜ他の何かに答えますか?また、最初の段落は正しくありません:はい、grepで実行できます。
fcm

32
    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

1
sedの例の+1は、grepよりも仕事に適したツールのようです。一つのコメント、^および$ので、余分ですが.*貪欲な試合です。ただし、それらを含めると、正規表現の意図を明確にするのに役立つ場合があります。
トニー

18

foob​​arが常に最初の単語または行であることがわかっている場合は、cutを使用できます。そのようです:

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

-ogrep の切り替えは(Gnu grep拡張機能よりも)広く実装されているためgrep -o "foobar" test.file | cut -d" " -f2、このソリューションの有効性が向上し、後読みアサーションを使用するよりも移植性が高くなります。
-dubiousjim

私はあなたが必要とするだろうと信じているgrep -o "foobar .*」かgrep -o "foobar \w+"
G-マン

9

PCREがサポートされていない場合、grepを2回呼び出しても同じ結果が得られます。たとえば、foob​​arの後の単語を取得するには、次のようにします。

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

これは、foob​​arの後の任意の単語に展開できます(読みやすいようにEREを使用):

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

出力:

1

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


6

pcregrep-o出力するキャプチャグループを選択できる、よりスマートなオプションがあります。したがって、サンプルファイルを使用して、

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

4

/ はBSDではなくGNUでのみ使用可能なgrepため、Using はクロスプラットフォーム互換ではありません。-P--perl-regexpgrepgrep

を使用したソリューションは次のripgrepとおりです。

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

としてman rg

-r/ --replace REPLACEMENT_TEXTすべての一致を指定されたテキストに置き換えます。

キャプチャグループインデックス(例:)$5および名前(例$foo:)は、置換文字列でサポートされています。

関連:GH-462


2

@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つだけだと思います。


これはまさに私がやろうとしていたことです。ありがとう!
ジェームズ

grep'TouchPad'が 'id'の左側にあると仮定した場合の、余分なものを持たない少しシンプルなバージョン:echo "SynPS/2 Synaptics TouchPad id=19 [slave pointer (2)]" | sed -nE "s/.*TouchPad.+id=([0-9]+).*/\1/p"
Amit Naidu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.