it-swarm.com.de

Kann grep nur bestimmte Gruppierungen ausgeben, die übereinstimmen?

Angenommen, ich habe eine Datei:

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

Ich möchte nur wissen, welche Wörter nach "foobar" erscheinen, damit ich diesen regulären Ausdruck verwenden kann:

"foobar \(\w\+\)"

Die Klammern zeigen an, dass ich direkt nach foobar ein besonderes Interesse an dem Wort habe. Aber wenn ich eine grep "foobar \(\w\+\)" test.txt mache, bekomme ich die gesamten Zeilen, die mit der gesamten Regex übereinstimmen, und nicht nur "das Wort nach der Foobar":

foobar bash 1
foobar happy

Ich würde es sehr bevorzugen, wenn die Ausgabe dieses Befehls so aussieht:

bash
happy

Gibt es eine Möglichkeit, grep anzuweisen, nur die Elemente, die der Gruppierung (oder einer bestimmten Gruppierung) entsprechen, in einem regulären Ausdruck auszugeben?

338
Cory Klein

GNU grep verfügt über die Option -P Für reguläre Ausdrücke im Perl-Stil und die Option -o, Um nur das zu drucken, was dem Muster entspricht. Diese können mit Look-Around-Zusicherungen (beschrieben unter Erweiterte Muster in der Perlre-Manpage ) kombiniert werden, um einen Teil des Grep-Musters aus dem zu entfernen, von dem festgestellt wurde, dass es für die Zwecke von -o Übereinstimmt. .

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

\K Ist die Kurzform (und effizientere Form) von (?<=pattern), Die Sie als Look-Behind-Zusicherung mit einer Breite von Null vor dem Text verwenden, den Sie ausgeben möchten. (?=pattern) Kann als Vorausschau-Zusicherung mit einer Breite von Null nach dem Text verwendet werden, den Sie ausgeben möchten.

Wenn Sie beispielsweise das Wort zwischen foo und bar abgleichen möchten, können Sie Folgendes verwenden:

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

oder (aus Symmetriegründen)

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

Standard grep kann dies nicht, aber neuere Versionen von GNU grep can . Sie können sich an sed, awk oder Perl wenden. Hier sind einige Beispiele, die was tun Sie möchten, dass sich Ihre Sample-Eingabe in Eckfällen etwas anders verhält.

Ersetzen Sie foobar Word other stuff von Word, nur drucken, wenn ein Austausch erfolgt ist.

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

Wenn das erste Wort foobar ist, drucken Sie das zweite Wort.

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

Entfernen Sie foobar, wenn es das erste Wort ist, und überspringen Sie die Zeile ansonsten. Entfernen Sie dann alles nach dem ersten Leerzeichen und drucken Sie.

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

Wenn Sie wissen, dass Foobar immer das erste Wort oder die erste Zeile ist, können Sie cut verwenden. Wie so:

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

pcregrep hat ein schlaueres -o Option, mit der Sie auswählen können, welche Erfassungsgruppen ausgegeben werden sollen. Verwenden Sie also Ihre Beispieldatei.

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

Wenn PCRE nicht unterstützt wird, können Sie mit zwei Aufrufen von grep dasselbe Ergebnis erzielen. Um beispielsweise das Wort nach foobar zu erfassen, gehen Sie wie folgt vor:

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

Dies kann nach foobar wie folgt zu einem beliebigen Wort erweitert werden (mit EREs zur besseren Lesbarkeit):

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

Ausgabe:

1

Beachten Sie, dass der Index i auf Null basiert.

9
Thor

Die Verwendung von grep ist nicht plattformübergreifend kompatibel, da -P/--Perl-regexp ist nur verfügbar für GNU grep , nicht BSD grep .

Hier ist die Lösung mit ripgrep :

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

Gemäß man rg:

-r/--replace REPLACEMENT_TEXT Ersetzen Sie jede Übereinstimmung durch den angegebenen Text.

Erfassen Sie Gruppenindizes (z. B. $5) und Namen (z. B. $foo) werden in der Ersatzzeichenfolge unterstützt.

Verwandte: GH-462 .

7
kenorb

Ich fand die Antwort von @jgshawkey sehr hilfreich. grep ist dafür kein so gutes Werkzeug, aber sed ist es, obwohl wir hier ein Beispiel haben, das grep verwendet, um eine relevante Zeile zu erfassen.

Die Regex-Syntax von sed ist eigenwillig, wenn Sie nicht daran gewöhnt sind.

Hier ist ein weiteres Beispiel: Dieses analysiert die Ausgabe von xinput, um eine ID-Ganzzahl zu erhalten

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

und ich will 19

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

Beachten Sie die Klassensyntax:

[[:digit:]]

und die Notwendigkeit, dem folgenden zu entkommen +

Ich gehe davon aus, dass nur eine Zeile übereinstimmt.

2
Tim Richardson