Wie kann man mit sed, awk oder gawk nur das drucken, was übereinstimmt?


100

Ich sehe viele Beispiele und Manpages, wie man Dinge wie Suchen und Ersetzen mit sed, awk oder gawk macht.

In meinem Fall habe ich jedoch einen regulären Ausdruck, den ich für eine Textdatei ausführen möchte, um einen bestimmten Wert zu extrahieren. Ich möchte nicht suchen und ersetzen. Dies wird von Bash aufgerufen. Verwenden wir ein Beispiel:

Beispiel für einen regulären Ausdruck:

.*abc([0-9]+)xyz.*

Beispiel für eine Eingabedatei:

a
b
c
abc12345xyz
a
b
c

So einfach das klingt, ich kann nicht herausfinden, wie man sed / awk / gawk richtig nennt. Was ich gehofft hatte, ist aus meinem Bash-Skript heraus:

myvalue=$( sed <...something...> input.txt )

Dinge, die ich versucht habe, sind:

sed -e 's/.*([0-9]).*/\\1/g' example.txt # extracts the entire input file
sed -n 's/.*([0-9]).*/\\1/g' example.txt # extracts nothing

10
Wow ... Leute haben diese Frage mit -1 abgelehnt? Ist es wirklich so unangemessen von einer Frage?
Stéphane

Es scheint vollkommen angemessen, Regex und leistungsstarke Befehlszeilenprogramme wie sed / awk oder einen Editor wie vi, emacs oder teco zu verwenden, der eher der Programmierung als nur der Verwendung einer alten Anwendung ähnelt. IMO das gehört auf SO mehr als SU.
Veröffentlicht

Vielleicht wurde es abgelehnt, weil es in seiner ursprünglichen Form einige seiner Anforderungen nicht klar definiert hat. Dies ist immer noch nicht der Fall, es sei denn, Sie lesen die Kommentare des OP zu den Antworten (einschließlich derjenigen, die ich gelöscht habe, als die Dinge birnenförmig wurden).
Pavium

Antworten:


42

Mein sed(Mac OS X) hat nicht funktioniert +. Ich habe es *stattdessen versucht und ein pTag zum Drucken von Übereinstimmungen hinzugefügt :

sed -n 's/^.*abc\([0-9]*\)xyz.*$/\1/p' example.txt

Für die Zuordnung von mindestens einem numerischen Zeichen ohne +würde ich Folgendes verwenden:

sed -n 's/^.*abc\([0-9][0-9]*\)xyz.*$/\1/p' example.txt

Danke, das hat auch bei mir funktioniert, als ich * statt + verwendet habe.
Stéphane

2
... und die Option "p" zum Drucken der Übereinstimmung, von der ich auch nichts wusste. Danke noch einmal.
Stéphane

2
Ich musste dem entkommen +und dann funktionierte es für mich:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
Bis auf weiteres pausiert.

3
Das liegt daran, dass Sie kein modernes RE-Format verwenden, daher ist + ein Standardzeichen und Sie sollten dies mit der Syntax {,} ausdrücken. Sie können die Option use -E sed hinzufügen, um das moderne RE-Format auszulösen. Überprüfen Sie re_format (7), insbesondere den letzten Absatz von DESCRIPTION developer.apple.com/library/mac/#documentation/Darwin/Reference/…
anddam

33

Sie können sed verwenden, um dies zu tun

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n Drucken Sie die resultierende Zeile nicht aus
  • -rDies macht es so, dass Sie nicht die Flucht der Capture Group Parens haben ().
  • \1 die Erfassungsgruppenübereinstimmung
  • /g globales Spiel
  • /p Drucken Sie das Ergebnis

Ich habe mir ein Tool geschrieben , das das einfacher macht

rip 'abc(\d+)xyz' '$1'

3
Dies ist bei weitem die beste und am besten erklärte Antwort bisher!
Nik Reiman

Mit einigen Erklärungen ist es viel besser zu verstehen, was mit unserem Problem nicht stimmt. Danke !
r4phG

17

Ich perlmache es mir leichter. z.B

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/'

Dadurch wird Perl ausgeführt. Die -nOption weist Perl an, jeweils eine Zeile von STDIN einzulesen und den Code auszuführen. Die -eOption gibt die auszuführende Anweisung an.

Die Anweisung führt einen regulären Ausdruck in der gelesenen Zeile aus, und wenn er übereinstimmt, wird der Inhalt des ersten Satzes von Klammern ( $1) ausgedruckt .

Sie können dies tun, um mehrere Dateinamen am Ende auch. z.B

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/' example1.txt example2.txt


Danke, aber wir haben keinen Zugang zu Perl, weshalb ich nach sed / awk / gawk gefragt habe.
Stéphane

5

Wenn Ihre Version dies grepunterstützt, können Sie die -oOption verwenden, um nur den Teil einer Zeile zu drucken , der Ihrem regulären Ausdruck entspricht.

Wenn nicht, dann ist hier das Beste, was sedich mir vorstellen kann:

sed -e '/[0-9]/!d' -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

... die ohne Ziffern löscht / überspringt und für die verbleibenden Zeilen alle führenden und nachfolgenden nichtstelligen Zeichen entfernt. (Ich vermute nur, dass Sie beabsichtigen, die Nummer aus jeder Zeile zu extrahieren, die eine enthält).

Das Problem mit so etwas wie:

sed -e 's/.*\([0-9]*\).*/&/' 

.... oder

sed -e 's/.*\([0-9]*\).*/\1/'

... sedunterstützt nur "gierige" Übereinstimmungen ... also stimmt die erste. * mit dem Rest der Zeile überein. Solange wir keine negierte Zeichenklasse verwenden können, um eine nicht gierige Übereinstimmung zu erzielen ... oder eine Version sedmit Perl-kompatiblen oder anderen Erweiterungen der regulären Ausdrücke, können wir keine genaue Musterübereinstimmung mit dem Musterraum (einer Linie) extrahieren ).


Sie können einfach zwei Ihrer sedBefehle folgendermaßen kombinieren :sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
Bis auf weiteres angehalten.

Bisher wusste ich nichts über die Option -o auf grep. Gut zu wissen. Es wird jedoch das gesamte Spiel gedruckt, nicht das "(...)". Wenn Sie also mit "abc ([[: digit:]] +) xyz" übereinstimmen, erhalten Sie "abc" und "xyz" sowie die Ziffern.
Stéphane

Danke, dass du mich daran erinnert hast grep -o! Ich habe versucht, dies zu tun, sedund hatte Probleme damit, in einigen Zeilen mehrere Übereinstimmungen zu finden. Meine Lösung ist stackoverflow.com/a/58308239/117471
Bruno Bronosky

3

Sie können awkmit verwenden, match()um auf die erfasste Gruppe zuzugreifen:

$ awk 'match($0, /abc([0-9]+)xyz/, matches) {print matches[1]}' file
12345

Dies versucht, dem Muster zu entsprechen abc[0-9]+xyz. In diesem Fall werden die Slices im Array gespeichert matches, dessen erstes Element der Block ist [0-9]+. Da match() die Zeichenposition oder der Index zurückgegeben wird, an dem diese Teilzeichenfolge beginnt (1, wenn sie am Anfang der Zeichenfolge beginnt) , wird die printAktion ausgelöst.


Mit können grepSie einen Blick zurück und einen Blick nach vorne werfen:

$ grep -oP '(?<=abc)[0-9]+(?=xyz)' file
12345

$ grep -oP 'abc\K[0-9]+(?=xyz)' file
12345

Diese prüft das Muster , [0-9]+wenn es auftritt innerhalb abcund xyzund druckt nur die Ziffern.


2

Perl ist die sauberste Syntax, aber wenn Sie kein Perl haben (ich verstehe, nicht immer da), ist die einzige Möglichkeit, Gawk und Komponenten eines regulären Ausdrucks zu verwenden, die Verwendung der Gensub-Funktion.

gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file

Die Ausgabe der Beispiel-Eingabedatei erfolgt

12345

Hinweis: gensub ersetzt den gesamten regulären Ausdruck (zwischen dem //), daher müssen Sie das. * Vor und nach dem ([0-9] +) setzen, um Text vor und nach der Zahl in der Ersetzung zu entfernen.


2
Eine clevere, praktikable Lösung, wenn Sie Gawk verwenden müssen (oder wollen). Sie haben dies bemerkt, aber um klar zu sein: Nicht-GNU awk hat kein gensub () und unterstützt dies daher nicht.
Cincodenada

Nett! Es kann jedoch am besten sein, match()auf die erfassten Gruppen zuzugreifen. Siehe meine Antwort dazu.
Fedorqui 'SO hör auf zu schaden'

1

Wenn Sie Linien auswählen möchten, entfernen Sie die nicht gewünschten Bits:

egrep 'abc[0-9]+xyz' inputFile | sed -e 's/^.*abc//' -e 's/xyz.*$//'

Grundsätzlich werden die gewünschten Zeilen ausgewählt egrepund anschließend seddie Bits vor und nach der Nummer entfernt.

Sie können dies hier in Aktion sehen:

pax> echo 'a
b
c
abc12345xyz
a
b
c' | egrep 'abc[0-9]+xyz' | sed -e 's/^.*abc//' -e 's/xyz.*$//'
12345
pax> 

Update: Wenn Ihre tatsächliche Situation komplexer ist, müssen die REs natürlich geändert werden. Zum Beispiel, wenn Sie am Anfang und am Ende immer eine einzelne Zahl innerhalb von null oder mehr Nicht-Zahlen vergraben hatten:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

Interessant ... Es gibt also keine einfache Möglichkeit, einen komplexen regulären Ausdruck anzuwenden und genau das zurückzugewinnen, was im Abschnitt (...) enthalten ist. Denn während ich sehe, was Sie hier zuerst mit grep und dann mit sed gemacht haben, ist unsere reale Situation viel komplexer als das Ablegen von "abc" und "xyz". Der reguläre Ausdruck wird verwendet, da auf beiden Seiten des Textes, den ich extrahieren möchte, viele verschiedene Texte angezeigt werden können.
Stéphane

Ich bin sicher, es gibt einen besseren Weg, wenn die REs wirklich komplex sind. Wenn Sie ein paar weitere Beispiele oder eine detailliertere Beschreibung angeben würden, könnten wir unsere Antworten möglicherweise an Ihre Bedürfnisse anpassen.
Paxdiablo

0

Der Fall des OP gibt nicht an, dass es mehrere Übereinstimmungen in einer einzelnen Zeile geben kann, aber für den Google-Verkehr werde ich auch ein Beispiel dafür hinzufügen.

Da das OP eine Gruppe aus einem Muster extrahieren muss, sind für die Verwendung grep -o2 Durchgänge erforderlich. Aber ich finde das immer noch die intuitivste Art, die Arbeit zu erledigen.

$ cat > example.txt <<TXT
a
b
c
abc12345xyz
a
abc23451xyz asdf abc34512xyz
c
TXT

$ cat example.txt | grep -oE 'abc([0-9]+)xyz'
abc12345xyz
abc23451xyz
abc34512xyz

$ cat example.txt | grep -oE 'abc([0-9]+)xyz' | grep -oE '[0-9]+'
12345
23451
34512

Da die Prozessorzeit im Grunde genommen kostenlos ist, die Lesbarkeit für den Menschen jedoch von unschätzbarem Wert ist, neige ich dazu, meinen Code basierend auf der Frage zu überarbeiten: "In einem Jahr, was werde ich davon halten?" Für Code, den ich öffentlich oder mit meinem Team teilen möchte, werde ich sogar offen sein, man grepum herauszufinden, welche langen Optionen es gibt, und diese ersetzen. Wie so:grep --only-matching --extended-regexp


-1

Sie können es mit der Shell tun

while read -r line
do
    case "$line" in
        *abc*[0-9]*xyz* ) 
            t="${line##abc}"
            echo "num is ${t%%xyz}";;
    esac
done <"file"

-3

Für awk. Ich würde das folgende Skript verwenden:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }

Dies gibt nicht den numerischen Wert aus ([0-9+]), sondern die gesamte Zeile.
Mark Lakata

-3
gawk '/.*abc([0-9]+)xyz.*/' file

2
Das scheint nicht zu funktionieren. Es wird die gesamte Zeile anstelle der Übereinstimmung gedruckt.
Stéphane

In Ihrer Beispiel-Eingabedatei ist dieses Muster die gesamte Zeile. richtig??? wenn Sie wissen , das Muster in einem bestimmten Bereich sein wird: Verwenden Sie $ 1, $ 2 etc .. zB gawk ‚$ 1 ~ /.*abc([0-9]+)xyz.*/‘ Datei
ghostdog74
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.