it-swarm.com.de

Wie greife ich nach Gruppen von n Ziffern, aber nicht mehr als n?

Ich lerne Linux und habe eine Herausforderung, die ich anscheinend nicht alleine lösen kann. Hier ist es:

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

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

33
Buddha

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

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

Zum Beispiel würde (1) _1234a56789_ anzeigen, aber (2) nicht.


Wenn Sie alle Zeilen anzeigen möchten, die eine Folge von vier Ziffern enthalten, die selbst nicht zu einer längeren Folge von Ziffern gehört, haben Sie folgende Möglichkeiten:

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

Hierfür werden reguläre Perl-Ausdrücke verwendet, die Ubuntus grep ( GNU grep ) unterstützt über _-P_. Es passt weder zu Text wie _12345_ noch zu dem _1234_ oder _2345_, die Teil davon sind. Aber es wird mit dem _1234_ in _1234a56789_ übereinstimmen.

In Perl reguläre Ausdrücke:

  • _\d_ steht für eine beliebige Ziffer (kurz _[0-9]_ oder _[[:digit:]]_).
  • _x{4}_ entspricht x 4 mal. (_{_ _}_ Die Syntax ist nicht spezifisch für reguläre Perl-Ausdrücke; sie wird auch in erweiterten regulären Ausdrücken über _grep -E_ verwendet.) Also ist _\d{4}_ dasselbe wie _\d\d\d\d_.
  • _(?<!\d)_ ist eine negative Look-Behind-Behauptung mit der Breite Null. Es bedeutet "sofern nicht _\d_ vorangestellt".
  • _(?!\d)_ ist eine negative Vorausschau-Behauptung mit der Breite Null. Es bedeutet "wenn nicht gefolgt von _\d_".

_(?<!\d)_ und _(?!\d)_ stimmen nicht mit Text überein, der nicht aus vier Ziffern besteht. Stattdessen verhindern sie (wenn sie zusammen verwendet werden), dass eine Folge von vier Ziffern übereinstimmt, wenn sie Teil einer längeren Folge von Ziffern ist.

Nur den Look-Behind oder nur den Look-Ahead zu verwenden, ist unzureichend, da die ganz rechts oder ganz links liegende vierstellige Teilfolge immer noch übereinstimmen würde.

Ein Vorteil der Verwendung von Look-Behind- und Look-Ahead-Zusicherungen besteht darin, dass Ihr Muster nur den vierstelligen Folgen selbst und nicht dem umgebenden Text entspricht. Dies ist hilfreich, wenn Sie die farbige Hervorhebung verwenden (mit der Option _--color_).

_[email protected]:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4_

Standardmäßig in Ubuntu hat jeder Benutzer _alias grep='grep --color=auto'_ in seiner _~.bashrc_ Datei . Sie erhalten also automatisch eine Farbhervorhebung, wenn Sie einen einfachen Befehl ausführen, der mit grep beginnt (dh wenn Aliase erweitert werden) und Standardausgabe ist ein Terminal (das ist, worauf --color=auto prüft). Übereinstimmungen werden normalerweise rot hervorgehoben (in der Nähe von vermilion ), aber ich habe es kursiv dargestellt. Hier ist ein Screenshot:
Screenshot showing that grep command, with 12345abc789d0123e4 as output, with the 0123 highlighted in red.

Mit _-o_ können Sie sogar festlegen, dass grep nur passenden Text und nicht die gesamte Zeile druckt:

_[email protected]:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123
_

Alternativer Weg, Ohne Behauptungen zum Hinausschauen und Voraussehen

Wenn Sie jedoch:

  1. benötigen Sie einen Befehl, der auch auf Systemen ausgeführt werden kann, auf denen grep _-P_ nicht unterstützt, oder auf denen Sie keinen regulären Perl-Ausdruck verwenden möchten, nd
  2. sie müssen die vier Ziffern nicht speziell abgleichen. Dies ist normalerweise der Fall, wenn Sie nur Zeilen mit Übereinstimmungen anzeigen möchten: nd
  3. sind in Ordnung mit einer Lösung, die ein bisschen weniger elegant ist

... dann können Sie dies mit einem erweiterten regulären Ausdruck erreichen:

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

Dies entspricht vier Ziffern und dem sie umgebenden nichtstelligen Zeichen - oder dem Anfang oder Ende der Zeile. Speziell:

  • _[0-9]_ stimmt mit jeder Ziffer überein (wie _[[:digit:]]_ oder _\d_ in regulären Perl-Ausdrücken) und _{4}_ bedeutet "viermal". _[0-9]{4}_ entspricht also einer vierstelligen Folge.
  • _[^0-9]_ stimmt mit Zeichen überein, die nicht im Bereich von _0_ bis _9_ liegen. Es entspricht _[^[:digit:]]_ (oder _\D_ in regulären Perl-Ausdrücken).
  • _^_, wenn es nicht in _[_ _]_ Klammern steht, stimmt mit dem Anfang einer Zeile überein. In ähnlicher Weise stimmt _$_ mit dem Ende einer Zeile überein.
  • _|_ bedeutet oder und Klammern sind für die Gruppierung (wie in der Algebra). So entspricht _(^|[^0-9])_ dem Zeilenanfang oder einem nichtstelligen Zeichen, während _($|[^0-9])_ dem Zeilenende oder einem nichtstelligen Zeichen entspricht.

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

  • am Anfang der Zeile oder mit vorangestelltem Zeichen (_(^|[^0-9])_) nd
  • am Ende der Zeile oder gefolgt von einer Nicht-Ziffer (_($|[^0-9])_).

Wenn Sie dagegen alle Zeilen anzeigen möchten, die eine vierstellige Folge enthalten, jedoch nicht any mit mehr als vier Ziffern (auch wenn eine von einer anderen Folge nur getrennt ist) vier Ziffern), dann ist es konzeptionell Ihr Ziel, Linien zu finden, die zu einem Muster passen, aber nicht zu einem anderen.

Daher würde ich, selbst wenn Sie wissen, wie man es mit einem einzelnen Muster macht, die Verwendung eines zweiten Vorschlags wie matt's vorschlagen, greping für die beiden Muster separat.

Dabei profitieren Sie nicht stark von den erweiterten Funktionen der regulären Perl-Ausdrücke. Daher ziehen Sie es möglicherweise vor, diese nicht zu verwenden. In Übereinstimmung mit dem obigen Stil ist hier eine Verkürzung von mats Lösung mit _\d_ (und geschweiften Klammern) anstelle von _[0-9]_:

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

Da es _[0-9]_ verwendet, ist matt's way portabler - es funktioniert auf Systemen, auf denen grep keine regulären Perl-Ausdrücke unterstützt. Wenn Sie _[0-9]_ (oder _[[:digit:]]_) anstelle von _\d_ verwenden, aber weiterhin _{}_ verwenden, erhalten Sie die Portabilität von Matt's Way etwas präziser :

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

Alternative Methode mit einem einzigen Muster

Wenn Sie wirklich einen grep-Befehl bevorzugen, dann

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

... dann können Sie verwenden:

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

Das Flag _-x_ bewirkt, dass grep nur Zeilen anzeigt, bei denen die gesamte Zeile übereinstimmt (und keine Zeile enthält ​​eine Übereinstimmung).

Ich habe einen regulären Perl-Ausdruck verwendet, weil ich der Meinung bin, dass die Kürze von _\d_ und _\D_ in diesem Fall die Klarheit erheblich verbessern. Wenn Sie jedoch ein tragbares Gerät für Systeme benötigen, auf denen grep _-P_ nicht unterstützt, können Sie es durch _[0-9]_ und _[^0-9]_ (oder durch _[[:digit:]]_ und _[^[:digit]]_ ersetzen. ):

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

Diese regulären Ausdrücke funktionieren folgendermaßen:

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

  • Links 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. Nullstellen (d. H. Nichts) sind eine Möglichkeit für "nicht mehr als vier Stellen". Dies entspricht (a) der leeren Zeichenfolge oder (b) einer beliebigen Zeichenfolge Endung in einer Nicht-Ziffer und enthält keine Sequenzen von mehr als vier Ziffern.

    Da der Text unmittelbar links vom zentralen _\d{4}_ (oder _[0-9]{4}_) entweder leer sein oder mit einer Nicht-Ziffer enden muss, kann der zentrale _\d{4}_ nicht mit vier Ziffern übereinstimmen eine weitere (fünfte) Ziffer links von ihnen.

  • Auf der rechten Seite entspricht _(\D\d{0,4})*_ oder _([^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 sein könnten) , zwei, eins oder gar keine). Dies entspricht (a) der leeren Zeichenfolge oder (b) einer beliebigen Zeichenfolge am Anfang einer Nicht-Ziffer und enthält keine Folgen von mehr als vier Ziffern.

    Da der Text rechts neben dem zentralen _\d{4}_ (oder _[0-9]{4}_) entweder leer sein muss oder mit einer Nicht-Ziffer beginnt, kann der zentrale _\d{4}_ nicht mit vier Ziffern übereinstimmen eine weitere (fünfte) Ziffer rechts von ihnen.

Dies stellt sicher, dass irgendwo eine vierstellige Folge vorhanden ist und dass nirgendwo eine Folge von fünf oder mehr Ziffern vorhanden ist.

Es ist nicht schlecht oder falsch, es so zu machen. Der vielleicht wichtigste Grund, diese Alternative in Betracht zu ziehen, ist, dass der Nutzen der Verwendung von _grep -P '\d{4}' file | grep -Pv '\d{5}'_ (oder ähnlichem) verdeutlicht wird, wie oben und in Mats Antwort vorgeschlagen.

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

49
Eliah Kagan

Dies zeigt Ihnen 4 Zahlen hintereinander, aber nicht mehr

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

Beachten Sie, dass ^ nicht bedeutet

Es gibt ein Problem damit, obwohl ich nicht sicher bin, wie ich es beheben soll ... Wenn die Nummer das Ende der Zeile ist, 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]
8
matt

Wenn grep keine regulären Perl-Ausdrücke (-P) unterstützt, verwenden Sie den folgenden Shell-Befehl:

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

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

Mit -w werden die gesamten Wörter gesucht. Wenn Sie jedoch an alphanumerischen Zeichenfolgen wie 1234a interessiert sind, fügen Sie [^0-9] am Ende des Musters hinzu, z.

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

Die Verwendung von $() ist im Grunde ein Befehlsersetzung . Überprüfen Sie diese post , um zu sehen, wie printf das Muster wiederholt.

0
kenorb

Sie können den folgenden Befehl versuchen, indem Sie den tatsächlichen Dateinamen in Ihrem System ersetzen. Sie können auch dieses Tutorial für weitere Verwendungen des Befehls grep überprüfen:

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

0
Mike Tyson