Wie werden nur erfasste Gruppen mit sed ausgegeben?

277

Gibt es eine Möglichkeit, sednur erfasste Gruppen auszugeben? Zum Beispiel angesichts der Eingabe:

This is a sample 123 text and some 987 numbers

und Muster:

/([\d]+)/

Könnte ich nur 123- und 987-Ausgaben erhalten, die durch Rückverweise formatiert sind?

Pablo
quelle
Beachten Sie, dass für die Gruppenerfassung sederweiterte reguläre Ausdrücke mit dem -EFlag aktiviert werden müssen.
peterh - Monica vor

Antworten:

333

Der Schlüssel, um dies zum Laufen zu bringen, besteht seddarin, anzugeben, was nicht ausgegeben werden soll, und anzugeben, was Sie möchten.

string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Dies sagt:

  • Drucken Sie nicht standardmäßig jede Zeile ( -n)
  • Null oder mehr Nicht-Ziffern ausschließen
  • Geben Sie eine oder mehrere Ziffern an
  • eine oder mehrere Ziffern ausschließen
  • Geben Sie eine oder mehrere Ziffern an
  • Null oder mehr Nicht-Ziffern ausschließen
  • drucke die Ersetzung aus ( p)

Im Allgemeinen sederfassen Sie Gruppen in Klammern und geben das, was Sie erfassen, mithilfe einer Rückreferenz aus:

echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'

gibt "bar" aus. Wenn Sie -r( -Efür OS X) für erweiterten regulären Ausdruck verwenden, müssen Sie die Klammern nicht umgehen:

echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'

Es können bis zu 9 Erfassungsgruppen und deren Rückverweise vorhanden sein. Die Rückverweise sind in der Reihenfolge nummeriert, in der die Gruppen angezeigt werden. Sie können jedoch in beliebiger Reihenfolge verwendet und wiederholt werden:

echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'

gibt "a bar a" aus.

Wenn Sie GNU haben grep(es kann auch in BSD funktionieren, einschließlich OS X):

echo "$string" | grep -Po '\d+'

oder Variationen wie:

echo "$string" | grep -Po '(?<=\D )(\d+)'

Die -POption aktiviert Perl-kompatible reguläre Ausdrücke. Siehe man 3 pcrepatternoder man 3 pcresyntax.

Bis auf weiteres angehalten.
quelle
24
OSX Mountain Lion unterstützt PCRE in grep nicht mehr.
Yincrash
1
Nebenbei bemerkt, die Option grep -o wird unter Solaris 9 nicht unterstützt. Außerdem unterstützt Solaris 9 die Option sed -r nicht. :(
Daniel Kats
7
Bitten Sie Ihren Systemadministrator, gsed zu installieren. Sie werden erstaunt sein, was ein paar Donuts Ihnen bringen werden ...
avgvstvs
3
Beachten Sie, dass Sie möglicherweise '\' und ')' mit '\' voranstellen müssen. Ich weiß nicht warum.
Lumbric
7
@lumbric: Wenn Sie sich auf das sedBeispiel beziehen und die -rOption (oder -Efür OS X, IIRC) verwenden, müssen Sie die Klammern nicht umgehen. Der Unterschied besteht darin, dass zwischen regulären Grundausdrücken und erweiterten regulären Ausdrücken ( -r).
Bis auf weiteres angehalten.
55

Sed hat bis zu neun gespeicherte Muster, aber Sie müssen maskierte Klammern verwenden, um sich Teile des regulären Ausdrucks zu merken.

Sehen Sie hier für Beispiele und detaillierten

Peter McG
quelle
58
sed -e 's/version=\(.+\)/\1/' input.txtDies wird immer noch die gesamte input.txt
Pablo
@Pablo, In dein Muster musst du \+statt schreiben +. Und ich verstehe nicht, warum Leute -enur für einen sed-Befehl verwenden.
Fredrick Gauss
1
Verwendung sed -e -n 's/version=\(.+\)/\1/p' input.txtsiehe: mikeplate.com/2012/05/09/…
awattar
1
Ich würde vorschlagen sed -E, die sogenannten "modernen" oder "erweiterten" regulären Ausdrücke zu verwenden, die Perl / Java / JavaScript / Go / was auch immer viel näher kommen. (Vergleiche mit grep -Eoder egrep.) Die Standardsyntax enthält diese seltsamen Escape-Regeln und gilt als "veraltet". Weitere Informationen zu den Unterschieden zwischen den beiden finden Sie unter man 7 re_format.
AndrewF
31

Sie können grep verwenden

grep -Eow "[0-9]+" file
Ghostdog74
quelle
4
@ Ghostdog74: Stimme dir absolut zu. Wie kann ich greo dazu bringen, nur erfasste Gruppen auszugeben?
Pablo
1
@Michael - deshalb gibt es die oOption - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, --only-match Zeigt nur den Teil einer übereinstimmenden Zeile an, der mit PATTERN übereinstimmt
Bert F
14
@ Bert F: Ich verstehe den passenden Teil, aber er erfasst keine Gruppe. Was ich möchte, ist, dass es so ist ([0-9] +). + ([Abc] {2,3}), also gibt es 2 Erfassungsgruppen. Ich möchte NUR das Erfassen von Gruppen durch Rückreferenzen oder auf andere Weise ausgeben.
Pablo
Hallo Michael. Haben Sie es geschafft, die n-te erfasste Gruppe per grep zu extrahieren?
doc_id
1
@Pablo: grep gibt nur aus, was passt. Verwenden Sie mehrere Ausdrücke, um mehrere Gruppen zu erhalten: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"Ich weiß nicht, wie Sie diese beiden Ausdrücke in einer Zeile haben können, abgesehen von der Weiterleitung von einem vorherigen grep (was immer noch nicht funktionieren könnte, wenn eines der Muster mehr als einmal in einer Zeile übereinstimmt ).
idbrii
13

Lauf (e) von Ziffern

Diese Antwort funktioniert mit einer beliebigen Anzahl von Zifferngruppen. Beispiel:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Erweiterte Antwort.

Gibt es eine Möglichkeit, sed anzuweisen, nur erfasste Gruppen auszugeben?

Ja. Ersetzen Sie den gesamten Text durch die Erfassungsgruppe:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

Oder mit erweiterter Syntax (weniger Anführungszeichen und die Verwendung von +):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

Verwenden Sie Folgendes, um zu vermeiden, dass der Originaltext gedruckt wird, wenn keine Nummer vorhanden ist:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Druckt die Eingabe nicht standardmäßig.
  • (/ p) Nur drucken, wenn ein Austausch durchgeführt wurde.

Und um mehrere Zahlen abzugleichen (und sie auch auszudrucken):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

Das funktioniert für jede Anzahl von Ziffernläufen:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Welches ist dem Befehl grep sehr ähnlich:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

Über \ d

und Muster: /([\d]+)/

Sed erkennt die Syntax '\ d' (Verknüpfung) nicht. Das oben verwendete ASCII-Äquivalent [0-9]ist nicht genau äquivalent. Die einzige alternative Lösung besteht darin, eine Zeichenklasse zu verwenden: '[[: digit:]] `.

Die ausgewählte Antwort verwendet solche "Zeichenklassen", um eine Lösung zu erstellen:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Diese Lösung funktioniert nur für (genau) zwei Ziffernläufe.

Während die Antwort in der Shell ausgeführt wird, können wir natürlich einige Variablen definieren, um diese Antwort zu verkürzen:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

Wie bereits erläutert, ist die Verwendung eines s/…/…/gpBefehls jedoch besser:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

Dies umfasst sowohl wiederholte Ziffernläufe als auch das Schreiben eines kurzen (er) Befehls.

Isaac
quelle
Nachdem ich die Antwort mit der hohen Stimmenzahl gelesen hatte, war ich überrascht, nach unten zu scrollen, um über ihren engen Umfang zu schreiben und den Geist der Frage tatsächlich anzusprechen. Ich hätte ahnen sollen, dass es schon vor Jahren jemand getan hätte. Dies ist sehr gut erklärt und die wahre richtige Antwort.
Amit Naidu
9

Ich glaube, das in der Frage angegebene Muster diente nur als Beispiel, und das Ziel war es, mit jedem Muster übereinzustimmen.

Wenn Sie ein Sed mit der GNU-Erweiterung haben, das das Einfügen einer neuen Zeile in den Musterbereich ermöglicht, lautet ein Vorschlag:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

Diese Beispiele sind mit tcsh (ja, ich weiß, es ist die falsche Shell) mit CYGWIN. (Bearbeiten: Entfernen Sie für Bash set und die Leerzeichen um =.)

Joseph Quinsey
quelle
@ Joseph: Danke, aber aufgrund meiner Aufgabe denke ich, dass Grep natürlicher ist, wie es Ghostdog74 vorgeschlagen hat. Sie müssen nur herausfinden, wie grep nur die Erfassungsgruppen ausgibt, nicht die gesamte Übereinstimmung.
Pablo
2
Nur eine Notiz, aber das Pluszeichen '+' bedeutet 'eins oder mehrere', wodurch die Notwendigkeit entfällt, sich in den Mustern zu wiederholen. Also würde "[0-9] [0-9] *" zu "[0-9] +"
RandomInsano
4
@RandomInsano: Um das zu verwenden +, müssten Sie es maskieren oder die -rOption ( -Efür OS X) verwenden. Sie können auch verwenden \{1,\}(oder -roder -Eohne die Flucht).
Bis auf weiteres angehalten.
9

Gib auf und benutze Perl

Da sedes nicht schneidet, werfen wir einfach das Handtuch und verwenden Perl, zumindest ist es LSB, während grepGNU-Erweiterungen nicht sind :-)

  • Drucken Sie das gesamte passende Teil aus, es sind keine passenden Gruppen oder Looks erforderlich:

    cat <<EOS | perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS

    Ausgabe:

    12
    3456
  • Einzelübereinstimmung pro Zeile, häufig strukturierte Datenfelder:

    cat <<EOS | perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS

    Ausgabe:

    1
    34

    Mit Lookbehind:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • Mehrere Felder:

    cat <<EOS | perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS

    Ausgabe:

    1 2
    34 56
  • Mehrere Übereinstimmungen pro Zeile, häufig unstrukturierte Daten:

    cat <<EOS | perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Ausgabe:

    1 
    34 78

    Mit Lookbehind:

    cat EOS<< | perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Ausgabe:

    1
    3478
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
1
Was hast du mit dem Ende der Frage nicht bekommen: "mit sed"?
Moonchild
@ Moonchild Googler ist das egal.
Ciro Santilli 法轮功 冠状 病 六四 事件 5
1
Ich fand das nützlich. Nicht alle Regex-Probleme in der Befehlszeile müssen mit sed gelöst werden.
PPPaul
5

Versuchen

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

Ich habe das unter Cygwin:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$
Bert F.
quelle
2

Es ist nicht das, wonach das OP gefragt hat (Erfassen von Gruppen), aber Sie können die Zahlen extrahieren, indem Sie:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

Gibt Folgendes:

123
987
Thomas Bratt
quelle