Suchen von Text zwischen zwei bestimmten Zeichen oder Zeichenfolgen

17

Angenommen, ich habe Zeilen wie diese:

*[234]*
*[23]*
*[1453]*

wobei *für eine beliebige Zeichenkette ( mit Ausnahme einer Reihe von der Form [number]). Wie kann ich diese Zeilen mit einem Befehlszeilendienstprogramm analysieren und die Zahl in Klammern extrahieren?

Ganz allgemein, die dieser Werkzeuge cut, sed, grepoder awkwürde für eine solche Aufgabe geeignet sein?

Amelio Vazquez-Reina
quelle

Antworten:

16

Wenn Sie GNU grep haben, können Sie mit seiner -oOption nach einem regulären Ausdruck suchen und nur den passenden Teil ausgeben. (Andere grep-Implementierungen können nur die gesamte Zeile anzeigen.) Wenn sich mehrere Übereinstimmungen in einer Zeile befinden, werden sie in separaten Zeilen gedruckt.

grep -o '\[[0-9]*\]'

Wenn Sie nur die Ziffern und nicht die Klammern wollen, ist es etwas schwieriger; Sie müssen eine Behauptung mit der Breite Null verwenden: einen regulären Ausdruck, der mit der leeren Zeichenfolge übereinstimmt, jedoch nur, wenn eine Klammer vor bzw. nach dieser steht. Zusicherungen ohne Breite sind nur in Perl-Syntax verfügbar.

grep -P -o '(?<=\[)[0-9]*(?=\])'

Bei sed müssen Sie den Druck mit ausschalten -nund die gesamte Zeile abgleichen und nur den passenden Teil behalten. Wenn mehrere Übereinstimmungen in einer Zeile möglich sind, wird nur die letzte Übereinstimmung gedruckt. Weitere Informationen zur Verwendung von sed finden Sie unter Extrahieren eines Regex, der mit "sed" übereinstimmt, ohne die umgebenden Zeichen zu drucken .

sed -n 's/^.*\(\[[0-9]*\]\).*/\1/p'

oder wenn Sie nur die Ziffern und nicht die Klammern wollen:

sed -n 's/^.*\[\([0-9]*\)\].*/\1/p'

Ohne grep -oist Perl das Werkzeug der Wahl, wenn Sie etwas wollen, das sowohl einfach als auch verständlich ist. -nEnthält die Zeile in jeder Zeile ( ) eine Übereinstimmung für \[[0-9]*\], geben Sie diese Übereinstimmung ( $&) und eine neue Zeile ( -l) aus.

perl -l -ne '/\[[0-9]*\]/ and print $&'

Wenn Sie nur die Ziffern verwenden möchten, setzen Sie Klammern in den regulären Ausdruck, um eine Gruppe abzugrenzen, und drucken Sie nur diese Gruppe.

perl -l -ne '/\[([0-9]*)\]/ and print $1'

PS Wenn Sie nur eine oder mehrere Ziffern in Klammern benötigen, wechseln Sie [0-9]*zu [0-9][0-9]*oder zu [0-9]+in Perl.

Gilles 'SO - hör auf böse zu sein'
quelle
Alles ist gut, außer dass er will „die Nummer extrahiert zwischen Klammern“. Ich denke, "außer [number]" bedeutet außer[0-9]
Peter.O
1
@ Peter.OIch verstehe "außer [Nummer]", um zu bedeuten, dass es keine anderen Teile der Zeile dieser Form gibt. Aber ich habe meine Antwort bearbeitet, um zu zeigen, wie man nur die Ziffern druckt, nur für den Fall.
Gilles 'SO- hör auf böse zu sein'
1
Diese perlRegex-Behauptungen sehen wirklich nützlich aus! Ich habe darüber gelesen, nachdem Sie gesehen haben, dass Sie sowohl Rückwärts- als auch Vorwärtsaussagen verwenden, auch in grep (ich habe die Tatsache ausgeschaltet, dass Sie eine Regex-Engine auswählen können). Ich werde von jetzt an etwas mehr Zeit für Perls Regex verwenden. Danke ... PS .. Ich habe gerade gelesen man grep... "Dies ist sehr experimentell und grep -P kann vor nicht implementierten Funktionen warnen." ... ich hoffe das heißt nicht instabil (?) ...
Peter.O
5

Sie können es nicht mit tun cut.

  1. tr -c -d '0123456789\012'
  2. sed 's/[^0-9]*//g'
  3. awk -F'[^0-9]+' '{ print $1$2$3 }'
  4. grep -o -E '[0-9]+'

tr ist die natürlichste Lösung für das Problem und würde wahrscheinlich am schnellsten laufen, aber ich denke, Sie würden gigantische Eingaben benötigen, um eine dieser Optionen in Bezug auf die Geschwindigkeit zu trennen.

Kyle Jones
quelle
Für sed, ^.*ist gierig und verbraucht alles außer der letzten Ziffer und +muss sein \+oder sonst die Posix verwenden \([0-9][0-9]*\).... und 's/[^0-9]*//g'funktioniert auf jeden Fall genauso gut, ... Thanks for the tr -c` Beispiel, aber ist das nicht \012überflüssig?
Peter.O
@ Peter Danke, dass du das erwischt hast. Ich hätte geschworen, dass ich das Sed-Beispiel getestet habe. :( Ich habe es auf Ihre Version geändert. In Bezug auf \012: es wird benötigt, sonst trwerden die Zeilenumbrüche essen.
Kyle Jones
Aha ... ich sah es als \0, 1, 2(oder sogar \, 0, 1, 2). Ich bin nicht gut genug auf Oktal eingestellt, wie es scheint. Danke.
Peter.O
4

Wenn Sie meinen, einen Satz aufeinanderfolgender Ziffern zwischen nichtstelligen Zeichen zu extrahieren, denke ich sedund bin awkder Beste (obwohl greper Ihnen auch die übereinstimmenden Zeichen geben kann):

sed: Sie können natürlich die Ziffern abgleichen, aber es ist vielleicht interessant, das Gegenteil zu tun und die nicht-Ziffern zu entfernen (funktioniert, solange es nur eine Ziffer pro Zeile gibt):

$ echo nn3334nn | sed -e 's/[^[[:digit:]]]*//g'
3344

grep: Sie können aufeinanderfolgende Ziffern abgleichen

$ echo nn3334nn | grep -o '[[:digit:]]*'
3344

Ich gebe kein Beispiel dafür, awkweil ich keine Erfahrung damit habe; Es ist interessant zu bemerken, dass dies, obwohl sedes sich um ein Schweizer Messer handelt, grepeinfacher und besser lesbar ist. Dies funktioniert auch für mehr als eine Zahl in jeder Eingabezeile (es werden -onur die jeweils passenden Teile der Eingabe gedruckt) in eigener Zeile):

$ echo dna42dna54dna | grep -o '[[:digit:]]*'
42
54
njsg
quelle
So wie ein Vergleich, hier ist ein sedeqivalent des „mehr als eine Nummer pro Zeile“ Beispiels grep -o '[[:digit:]]*'. . . sed -nr '/[0-9]/{ s/^[^[0-9]*|[^0-9]*$//g; s/[^0-9]+/\n/g; p}'... (+1)
Peter.O
2

Da gesagt wurde, dass dies nicht möglich ist cut, werde ich zeigen, dass es leicht möglich ist, eine Lösung zu finden, die zumindest nicht schlechter ist als einige der anderen, auch wenn ich die Verwendung cutals "Beste" nicht befürworte. (oder sogar eine besonders gute) Lösung. Es sollte gesagt werden, dass jede Lösung, die nicht speziell nach *[und ]*um die Ziffern sucht, vereinfachende Annahmen macht und daher dazu neigt, bei Beispielen zu scheitern, die komplexer sind als die vom Fragesteller angegebenen (z. B. Ziffern außerhalb von *[und ]*, die nicht gezeigt werden sollten). Diese Lösung überprüft mindestens die Klammern und kann erweitert werden, um auch die Sternchen zu überprüfen (als Übung für den Leser):

cut -f 2 -d '[' myfile.txt | cut -f 1 -d ']'

Hierbei wird die -dOption verwendet, die ein Trennzeichen angibt. Natürlich können Sie auch in den cutAusdruck einfügen, anstatt aus einer Datei zu lesen. Während cutwahrscheinlich ziemlich schnell, da es einfach (kein Regex - Engine) ist, müssen Sie es mindestens zweimal (oder ein paar mehr Zeit zu überprüfen aufrufen *), die einen Prozess Overhead erzeugt. Der eigentliche Vorteil dieser Lösung besteht darin, dass sie besonders für Gelegenheitsanwender, die sich mit Regex-Konstrukten nicht auskennen, gut lesbar ist.

Thomas
quelle