web-dev-qa-db-ja.com

PCRE-regexキャプチャグループを除外するには、grepを使用します

GNU grep-P PCRE Regexを使用して、ファイルの文字列を照合しています。入力ファイルには、次のような文字列を含む行があります。

FOO_1BAR.Zoo.2.someString:More-RandomString (string here too): 0.45654343

上記の行から20.45654343の数字をキャプチャしたいと思います。私は正規表現を使用しました

grep -Po ".Zoo.\K[\d+](.*):\ (.*)$" file

しかし、これは私に結果をもたらしています

2.someString:More-RandomString (string here too): 0.45654343

最初のキャプチャグループから最初の番号を2として取得し、行末のキャプチャグループと照合することもできます。しかし、2つのキャプチャグループ間の単語/行をスキップすることはできません。

真ん中にそれらの単語をキャプチャしているグループ(.*)があることは知っています。私がやろうとしたことは、それを無視する別の\Kを含めることです

grep -Po ".Zoo.\K[\d+](.*):\K (.*)$" file

しかし、それでは0.556984として2番目のキャプチャグループしか得られませんでした。

また、(?:)構文を使用した非キャプチャグループも

grep -Po ".Zoo.\K[\d+](?=.someString:More-RandomString (string here too)):\ (.*)$"

しかし、これは私に何も与えませんでした。ここで何が欠けていますか?

6
Inian

grepの名前は、g/re/pedコマンドの後に続きます。その主な目的は、正規表現に一致する行を出力することです。それらの行の内容を編集することはその役割ではありません。 sed(ストリームエディター)またはawkがあります。

現在、GNU grepで始まるいくつかのgrep実装は、-oオプションを追加して、各行の一致した部分(キャプチャグループではなく正規表現によって一致したもの)を出力します)。 GNUのようなgrep実装(-Pを使用)または正規表現のPCREをサポートするpcregrepを再び取得しました。

pcregrepは実際に-o<n>オプションを追加して、キャプチャグループのコンテンツを出力しました。だからあなたはできる:

pcregrep -o1 -o2 --om-separator=' ' '.Zoo.(\d+).*:\s+(.*)'

しかし、ここでは、明らかな標準的な解決策はsedを使用することです。

sed -n 's/^.*\.Zoo\.\([0-9]\{1,\}\).*:[[:space:]]\{1,\}/\1 /p'

または、Perl正規表現が必要な場合は、Perlを使用します。

Perl -lne 'print "$1 $2" if /\.Zoo\.(\d+).*:\s+(.*)/'

GNU grepを使用すると、一致が別の行に表示されることを気にしない場合は、次のことができます。

$ grep -Po '\.Zoo\.\K\d+|:\s+\K.*' < file
2
0.45654343

\Kは一致した部分の開始をリセットしますが、これは、交互の2つの部分が重なり合うことで回避できるという意味ではありません。

grep -Po '.Zoo。(\ K\d + | .:\ K。)'

echo foobar | grep -Po 'foo|foob'が機能しないように機能しません(foofoobの両方を出力する場合)。 foo|foobは、最初にfooに一致し、次にgrepは、fooの後の入力で他の潜在的な一致を探します。つまり、bbarから開始するため、それ以降は検索できません。

上記のgrep -Po '\.Zoo\.\K\d+|:\s+\K.*'では、代替の2番目の部分で:<spaces><anything>のみが検索されます。これは.Zoo.<digits>の後の部分で一致しますが、:<spaces><anything>の後に続くときだけでなく、入力内のどこでも.Zoo.<digits>を見つけることを意味します。

ただし、別のPCRE特殊演算子\Gを使用して、これを回避する方法があります。 \Gは件名の先頭で一致します。単一の一致の場合、これは^と同等ですが、複数の一致がある場合(s/.../.../gsed/Perlgフラグを考えてください)、-oと同様に、grepが行は、前の一致の終了後にも一致します。だからそれを作ったら:

grep -Po '\.Zoo\.\K\d+|(?!^)\G.*:\s+\K.*'

ここで、(?!^)行の先頭ではないを意味する否定的な先読み演算子であり、\Gは前回の(空でない)一致が成功した後にのみ一致します。したがって、.*:\s+\K.*は、前の一致が成功した場合にのみ一致します。代替のその他の部分は行の終わりまで一致するため、これは.foo.<digits>の1つにすぎません。

次のような入力で:

.Zoo.1.Zoo.2 tar: blah

それは出力します:

1
2
blah

でも。それを望まなかった場合は、代替の最初の部分も行の先頭でのみ一致させる必要があります。何かのようなもの

grep -Po '^.*?\.Zoo\.\K\d+|(?!^)\G.*:\s+\K.*'

それでも、2.Zoo.2 no colon characterなどの入力では.Zoo.2 blah:が出力されます。代替の最初の部分で先読み演算子を使用して回避し、:<spaces>の後に少なくとも1つの非スペースを探す(および$を使用して非文字の問題を回避する) )

grep -Po '^.*?\.Zoo\.\K\d+(?=.*:\s+\S.*$)|(?!^)\G.*:\s+\K\S.*$'

おそらく、その正規表現を説明するために数ページのコメントが必要になるでしょう。そのため、私はまっすぐ進むsed/Perlソリューションを探します...

10