Wie sucht man nach Gruppen von n Ziffern, aber nicht mehr als n?

Wie sucht man nach Gruppen von n Ziffern, aber nicht mehr als n?

Ich lerne Linux und stehe vor einer Herausforderung, die ich anscheinend nicht alleine lösen kann. Hier ist sie:

grep eine Zeile aus einer Datei, die 4 Zahlen in einer Reihe enthält, aber nicht mehr als 4.

Ich bin nicht sicher, wie ich das angehen soll. Ich kann nach bestimmten Zahlen suchen, aber nicht nach deren Betrag in einer Zeichenfolge.

Antwort1

Es gibt zwei Möglichkeiten, diese Frage zu interpretieren. Ich werde beide Fälle behandeln. Sie möchten möglicherweise Zeilen anzeigen:

  1. die eine Folge von vier Ziffern enthalten, die selbst nicht Teil einer längeren Ziffernfolge ist,oder
  2. die eine vierstellige Zahlenfolge enthält, jedoch keine längere Zahlenfolge (auch nicht einzeln).

Beispielsweise würde (1) anzeigen 1234a56789, (2) jedoch nicht.


Wenn Sie alle Zeilen anzeigen möchten, die eine Folge von vier Ziffern enthalten, die selbst nicht Teil einer längeren Ziffernfolge ist, haben Sie folgende Möglichkeit:

grep -P '(?<!\d)\d{4}(?!\d)' file

Dies verwendetReguläre Ausdrücke in Perl, die Ubuntusgrep(GNU grep) unterstützt über -P. Es wird weder mit Text wie abgeglichen 12345, noch mit 1234oder 2345, die Teil davon sind.Aber es wird mit dem 1234in übereinstimmen 1234a56789.

In regulären Ausdrücken in Perl:

  • \dbedeutet eine beliebige Ziffer (eine Kurzform für „ [0-9]oder“ [[:digit:]]).
  • x{4}Streichhölzerx4-mal. ( Die Syntax ist nicht spezifisch für reguläre Ausdrücke in Perl; sie ist auch { }in erweiterten regulären Ausdrücken via enthalten .) Ist also dasselbe wie .grep -E\d{4}\d\d\d\d
  • (?<!\d)ist eine negative Look-Behind-Assertion mit Nullbreite. Sie bedeutet „sofern nicht vorher ein \d.“
  • (?!\d)ist eine negative Vorausschaubehauptung mit Nullbreite. Sie bedeutet „sofern nicht gefolgt von \d.“

(?<!\d)und (?!\d)gleichen Text außerhalb einer vierstelligen Folge nicht ab; stattdessen verhindern sie (bei gemeinsamer Verwendung), dass eine eigene vierstellige Folge abgeglichen wird, wenn diese Teil einer längeren Ziffernfolge ist.

Die Verwendung nur des Look-Behind- oder nur des Look-Ahead-Algorithmus ist nicht ausreichend, da die äußerste rechte oder linke vierstellige Teilfolge dennoch übereinstimmen würde.

Ein Vorteil der VerwendungLook-Behind- und Look-Ahead-Behauptungenist, dass Ihr Muster nur mit den vierstelligen Sequenzen selbst übereinstimmt und nicht mit dem umgebenden Text. Dies ist hilfreich, wenn Sie Farbhervorhebungen verwenden (mit der --colorOption).

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

Standardmäßigin Ubuntu hat jeder Benutzer alias grep='grep --color=auto'in seinem~.bashrcDatei. Sie erhalten also automatisch eine Farbhervorhebung, wenn Sie einen einfachen Befehl ausführen, der mit beginnt grep(das ist der Fall, wennAliaseerweitert werden) undStandardausgabeIstein Terminal(das ist was--color=autosucht nach). Übereinstimmungen werden normalerweise in einem Rotton hervorgehoben (in der Nähe vonZinnober), aber ich habe es kursiv und fett dargestellt.Hier ist ein Screenshot:
Screenshot, der diesen Grep-Befehl mit der Ausgabe 12345abc789d0123e4 zeigt, wobei 0123 rot hervorgehoben ist.

Und Sie können sogar erreichen, dass grepnur der übereinstimmende Text und nicht die ganze Zeile gedruckt wird, mit -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

Alternativer Weg,OhneLook-Behind- und Look-Ahead-Behauptungen

Wenn Sie jedoch:

  1. Sie benötigen einen Befehl, der auch auf Systemen läuft, die reguläre Perl-Ausdrücke grepnicht unterstützen -Poder aus anderen Gründen nicht verwenden möchten,Und
  2. müssen nicht unbedingt mit den vier Ziffern übereinstimmen - was normalerweise der Fall ist, wenn Ihr Ziel einfach darin besteht, Zeilen mit Übereinstimmungen anzuzeigen,Und
  3. sind mit einer etwas weniger eleganten Lösung einverstanden

...dann erreichen Sie dies mit einemerweiterter regulärer Ausdruckstattdessen:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Dies entspricht vier Ziffern und dem sie umgebenden Nicht-Ziffernzeichen (oder dem Zeilenanfang bzw. Zeilenende). Genauer gesagt:

  • [0-9]passt zu jeder Ziffer (wie [[:digit:]], oder \din regulären Perl-Ausdrücken) und {4}bedeutet „viermal“. [0-9]{4}Passt also zu einer vierstelligen Zahlenfolge.
  • [^0-9]stimmt mit Zeichen überein, die nicht im Bereich von 0bis liegen 9. Dies ist gleichbedeutend mit [^[:digit:]](oder \Din regulären Perl-Ausdrücken).
  • ^Wenn es nicht in [ ]Klammern steht, entspricht es dem Anfang einer Zeile. Entsprechend $entspricht es dem Ende einer Zeile.
  • |bedeutetoderund Klammern dienen zur Gruppierung (wie in der Algebra). (^|[^0-9])Entspricht also dem Zeilenanfang oder einem nicht-stelligen Zeichen, während ($|[^0-9])Entspricht dem Zeilenende oder einem nicht-stelligen Zeichen.

Übereinstimmungen treten also nur in Zeilen auf, die eine vierstellige Zahlenfolge ( [0-9]{4}) enthalten, die gleichzeitig Folgendes ist:

  • am Zeilenanfang oder davor eine Nicht-Ziffer ( (^|[^0-9])),Und
  • am Ende der Zeile oder gefolgt von einer Nicht-Ziffer ( ($|[^0-9])).

Möchten Sie dagegen alle Zeilen anzeigen, die eine vierstellige Zahlenfolge enthalten, aber nichtbeliebigWenn Sie beispielsweise eine Folge von mehr als vier Ziffern haben (selbst wenn sie von einer anderen Folge von nur vier Ziffern verschieden ist), besteht Ihr Ziel konzeptionell darin, Zeilen zu finden, die einem Muster entsprechen, einem anderen jedoch nicht.

Selbst wenn Sie wissen, wie es mit einem einzigen Muster geht, würde ich daher empfehlen, etwas wieMattszweiter Vorschlag: grepFür die beiden Muster separat suchen.

Sie profitieren dabei nicht besonders von den erweiterten Funktionen der regulären Ausdrücke in Perl, daher möchten Sie diese vielleicht nicht verwenden. Aber um den obigen Stil beizubehalten, hier eine Verkürzung vonMatts LösungVerwendung von \d(und Klammern) anstelle von [0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

Da es verwendet [0-9],Matts Wegist portabler – es funktioniert auf Systemen, die grepreguläre Ausdrücke in Perl nicht unterstützen. Wenn Sie [0-9](oder [[:digit:]]) anstelle von verwenden \d, aber weiterhin verwenden { }, erhalten Sie die Portabilität von Matts Methode etwas prägnanter:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

Alternativer Weg mit einem einzigen Muster

grepWenn Sie wirklich einen Befehl bevorzugen, der

  1. verwendet einen einzelnen regulären Ausdruck(nicht zwei greps, getrennt durch einRohr, wie oben)
  2. Zeilen anzuzeigen, die mindestens eine Folge von vier Ziffern enthalten,
  3. aber keine Folgen von fünf (oder mehr) Ziffern,
  4. und es Ihnen nichts ausmacht, die ganze Zeile abzugleichen, nicht nur die Ziffern (das stört Sie wahrscheinlich nicht)

...dann können Sie Folgendes verwenden:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

Das -xFlag bewirkt, dass grepnur Zeilen angezeigt werden, bei denen die gesamte Zeile übereinstimmt (und nicht eine beliebige Zeile).enthaltendeine Übereinstimmung).

Ich habe einen regulären Perl-Ausdruck verwendet, da ich denke, dass die Kürze von \dund \Din diesem Fall die Übersichtlichkeit erheblich erhöhen. Wenn Sie jedoch etwas brauchen, das auf Systeme portierbar ist, die grepnicht unterstützen , können Sie sie durch und (oder durch und ) -Persetzen :[0-9][^0-9][[:digit:]][^[:digit]]

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

Die Funktionsweise dieser regulären Ausdrücke ist:

  • In der Mitte \d{4}oder [0-9]{4}entspricht einer Folge von vier Ziffern. Wir können mehr als eine davon haben, aber wir müssen mindestens eine haben.

  • Auf der linken Seite entspricht (\d{0,4}\D)*oder ([0-9]{0,4}[^0-9])*null oder mehr ( *) Instanzen von nicht mehr als vier Ziffern, gefolgt von einer Nicht-Ziffer. Null Ziffern (also nichts) sind eine Möglichkeit für „nicht mehr als vier Ziffern“. Dies entspricht(A)der leere String oder(B)beliebige ZeichenfolgeEndein einer Nicht-Ziffer und ohne Folgen mit mehr als vier Ziffern.

    Da der Text unmittelbar links neben der mittleren Ziffer \d{4}(oder [0-9]{4}) entweder leer sein oder mit einer Nicht-Ziffer enden muss, wird dadurch verhindert, \d{4}dass die mittlere Ziffer vier Ziffern zuordnet, die direkt links davon eine weitere (fünfte) Ziffer haben.

  • Auf der rechten Seite entspricht (\D\d{0,4})*or ([^0-9][0-9]{0,4})*null oder mehr ( *) Instanzen einer Nicht-Ziffer, gefolgt von nicht mehr als vier Ziffern (die, wie zuvor, vier, drei, zwei, eine oder sogar gar keine sein können). Dies entspricht(A)der leere String oder(B)beliebige ZeichenfolgeAnfangin einer Nicht-Ziffer und ohne Folgen mit mehr als vier Ziffern.

    Da der Text unmittelbar rechts neben der mittleren Ziffer \d{4}(oder [0-9]{4}) entweder leer sein oder mit einer Nicht-Ziffer beginnen muss, wird dadurch verhindert, \d{4}dass die mittlere Ziffer vier Ziffern zuordnet, die direkt rechts davon eine weitere (fünfte) Ziffer haben.

Dadurch wird sichergestellt, dass irgendwo eine vierstellige Zahlenfolge vorhanden ist, aber keine Zahlenfolge mit fünf oder mehr Ziffern.

Es ist weder schlecht noch falsch, es so zu machen. Aber der vielleicht wichtigste Grund, diese Alternative in Betracht zu ziehen, ist, dass sie den Nutzen der Verwendung von (oder ähnlich) verdeutlicht, wie oben und in vorgeschlagengrep -P '\d{4}' file | grep -Pv '\d{5}'Matts Antwort.

Auf diese Weise ist es klar, dass Ihr Ziel darin besteht, Zeilen auszuwählen, die eine Sache enthalten, aber nicht eine andere. Außerdem ist die Syntax einfacher (so dass sie von vielen Lesern/Betreuern möglicherweise schneller verstanden wird).

Antwort2

Dies zeigt Ihnen 4 Zahlen in einer Reihe, aber nicht mehr

grep '[0-9][0-9][0-9][0-9][^0-9]' file

Beachten Sie, dass das ^ nicht bedeutet

Allerdings gibt es hier ein Problem, bei dem ich nicht sicher bin, wie ich es beheben kann. Wenn die Nummer am Ende der Zeile steht, wird sie nicht angezeigt.

Diese hässlichere Version würde jedoch für diesen Fall funktionieren

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]

Antwort3

Sie können den folgenden Befehl ausprobieren, indem Sie ihn filedurch den tatsächlichen Dateinamen in Ihrem System ersetzen:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Sie können auch überprüfendieses Tutorialfür weitere Verwendungsmöglichkeiten des Grep-Befehls.

Antwort4

Wenn grepreguläre Ausdrücke in Perl () nicht unterstützt werden -P, verwenden Sie den folgenden Shell-Befehl:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

wobei printf '[0-9]%.0s' {1..4}4 mal ausgegeben wird [0-9]. Diese Methode ist nützlich, wenn Sie lange Ziffern haben und das Muster nicht wiederholen möchten (ersetzen Sie einfach 4durch die Anzahl der zu suchenden Ziffern).

Mit -wwird nach ganzen Wörtern gesucht. Wenn Sie jedoch an alphanumerischen Zeichenfolgen wie interessiert sind, 1234afügen Sie [^0-9]am Ende des Musters hinzu, z. B.

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

Die Verwendung $()ist im Grunde eineBefehlsersetzung. Überprüfen Sie diesPostum zu sehen, wie printfsich das Muster wiederholt.

verwandte Informationen