Wie kann ich Zeilen in einer Textdatei extrahieren / ändern, deren Daten in Felder unterteilt sind?

9

Wie kann ich feldbasierte Daten über die Befehlszeile bearbeiten? Zum Beispiel

  • Wie kann ich nur Zeilen drucken, deren N-tes Feld ist foo?
  • Wie kann ich nur Zeilen drucken, deren N-tes Feld nicht ist foo?
  • Wie kann ich nur Zeilen drucken, deren N-tes Feld übereinstimmt foo?
  • Wie kann ich Feld N in ändern foo?

Gibt es einen Standardansatz oder ein Toolset, mit dem feldbasierte Daten auf * nix-Systemen bearbeitet werden können?

terdon
quelle
4
Nein, das ist kein Betrug. Es befasst sich mit dem speziellen Fall von Daten, die in Feldern organisiert sind, und es geht nicht nur um das Ersetzen. Es geht auch darum, eine Teilmenge der Datei zu drucken.
Terdon
In Felder unterteilt wie? Die Antwort schlägt vor, durch Kommas (CSV), Tabulatoren, Leerzeichen oder ähnliches getrennt zu sein. Ich denke, ein Wort für spezialisierte Bibliotheken wie R oder Pythons Pandas, die CSV-Dateien usw. in Datenrahmen konvertieren, ist ebenfalls angebracht. Das sind wahrscheinlich die besten Lösungen insgesamt. Und ja, ich weiß, dass dort "von der Kommandozeile" stand.
Faheem Mitha
@FaheemMitha Ich wollte nur eine Einführung geben, die die Fähigkeiten von Tools wie awk, perl und sed für den Umgang mit feldbasierten Daten zeigt. Ich möchte keine fortgeschritteneren Techniken in meine Antwort aufnehmen, da ich es lieber einfach halten möchte. Ich würde es gerne lesen, also zögern Sie nicht, Ihre eigene Antwort mit R oder Python hinzuzufügen.
Terdon

Antworten:

9

Es gibt zwei grundlegende Ansätze, die beim Umgang mit Feldern verwendet werden können: i) Verwenden eines Tools, das Felder versteht; ii) Verwenden Sie einen regulären Ausdruck. Von den beiden ist der erstere normalerweise sowohl robuster als auch einfacher.

Viele der allgemein verfügbaren Tools auf * nix sind entweder explizit für den Umgang mit Feldern konzipiert oder verfügen über raffinierte Tricks, um dies zu vereinfachen.

1. Verwenden Sie ein Tool, das Felder versteht

1,1 awk

Das klassische Werkzeug hier ist awk. Es wird automatisch jede Eingangsleitung in Felder aufgeteilt (die Feldtrennstandardmäßig Leerzeichen sondern kann mit der ändernden -FFlag) und die Felder sind dann an das awkSkript als wo die Feldnummer. Das erste Feld ist , das zweite usw.$nn$1$2

  • Drucken Sie Zeilen, deren 3. Feld ist foo.

    awk '$3=="foo"' file

    Ändern des Trennzeichens in :

    awk -F":" '$3=="foo"' file

    Die Standardaktion von awkist das Drucken. Daher drucken die obigen Befehle alle Zeilen, deren 3. Feld ist foo. Bei der Verwendung -Fkönnen Sie beliebige Feldtrennzeichen festlegen und sogar reguläre Ausdrücke verwenden.

  • Wie kann ich nur Zeilen drucken, deren 3. Feld nicht ist foo?

    awk '$3!="foo"' file
  • Wie kann ich nur Zeilen drucken, deren 3. Feld übereinstimmt foo?

    Wenn Sie nur nach Feldern suchen, die einem Muster entsprechen (z. B. fooÜbereinstimmungen foobar), verwenden Sie ~anstelle von ==:

    awk '$3~/foo/' file
  • Wie kann ich nur Zeilen drucken, deren 3. Feld nicht übereinstimmt foo?

    awk '$3!~/foo/' file
  • Wie kann ich das 3. Feld ändern foo?

    awk '$3="foo"' file

1,2 Perl

Eine andere Wahl ist perlEinzeiler. Wie awk ist Perl eine voll funktionsfähige Skriptsprache, kann aber auch als Befehlszeilenprogramm ausgeführt werden, das ein Skript als Eingabe verwendet. Sein Verhalten wird durch Befehlszeilenschalter geändert, von denen die relevantesten für diese Frage sind:

  • -e: das Skript, das ausgeführt werden perlsoll;
  • -n : Lesen Sie die Eingabedatei Zeile für Zeile;
  • -p: drucke jede Eingabezeile nach dem Anwenden des Skripts von -e;
  • -l: Entfernen Sie nachfolgende Zeilenumbrüche aus jeder Eingabezeile und fügen Sie jedem printAnruf eine neue Zeile hinzu .
  • -a: awk-mode, teile jede Eingabezeile in das Array auf @F;
  • -F: das Feldtrennzeichen für -a.

Ein wichtiger Unterschied awkbesteht darin, dass perlder -aSwitch Dateien in ein Array aufteilt. In Perl beginnen Arrays bei 0, nicht bei 1. Dies bedeutet, dass das 2. Feld tatsächlich ist $F[1]und nicht $F[2]. In Anbetracht dessen sind die perlÄquivalente der oben genannten:

  • Drucken Sie Zeilen, deren 3. Feld ist foo.

    perl -ane 'print if $F[2] eq "foo"' file

    Ändern des Trennzeichens in :

    perl -F":" -ane 'print if $F[2] eq "foo"' file

    Im Gegensatz zu awk, perlkann keine reguläre Ausdrücke als Feldtrennzeichen verwenden. Sie müssen ein bestimmtes Zeichen oder eine bestimmte Zeichenfolge sein.

  • Wie kann ich nur Zeilen drucken, deren 3. Feld nicht ist foo?

    perl -ane 'print unless $F[2] eq "foo"' file
  • Wie kann ich nur Zeilen drucken, deren 3. Feld übereinstimmt foo?

    perl -ane 'print if $F[2]=~/foo/' file
  • Wie kann ich nur Zeilen drucken, deren 3. Feld nicht übereinstimmt foo?

    perl -lane 'print unless $F[2]=~/foo/' file
  • Wie kann ich das 3. Feld ändern foo?

    Dieser ist in Perl etwas umständlicher. Der übliche Ansatz besteht darin, den Wert im @FArray zu ändern und dann das Array zu drucken. Mit einfachen, durch Leerzeichen getrennten Dateien ist dies einfach:

    perl -lane '$F[2]="foo"; print "@F"' file

    Mit einem anderen Trennzeichen müssen Sie joindas Array. Andernfalls wird es durch Leerzeichen getrennt gedruckt:

    perl -F: -lane '$F[2]="foo"; print join ":",@F' file

2. Verwenden Sie reguläre Ausdrücke

Die Idee hier ist, einen regulären Ausdruck (kurz "Regex") zu verwenden, der die Position der Zielzeichenfolge in der Zeile definiert. Zum Beispiel :können wir in einer Datei, deren Felder durch getrennt sind, das 2. Feld finden, indem wir alles bis zum 1. :(dem 1. Feld) abgleichen und dann nach dem zweiten suchen:

^[^:]*:[^:]*:

Dieser reguläre Ausdruck bedeutet:

  • ^ : der Anfang der Zeile;
  • [^]: eine negierte Zeichenklasse. [^:]bedeutet "alles andere als :";
  • * : 0 oder mehr des vorherigen Musters;
  • :: ein wörtliches :;

Zusammengenommen bedeutet dies, dass das erste [^:]*das erste Feld und das zweite das zweite Feld ist. Dies ist natürlich nicht sehr praktisch, wenn Sie nach dem 14. Feld suchen, aber es kann für einfachere Dinge nützlich sein. Wie implementieren wir dies, um unsere Daten zu manipulieren? Es gibt verschiedene Tools, die dies tun können. in diesen Beispielen werde ich verwenden , sedaber Sie tun können , sehr ähnliche Dinge mit awk, perloder python.

  • Wie kann ich nur Zeilen drucken, deren 2. Feld ist foo?

    sed -n '/^[^:]*:foo:/p' file

    Das -nunterdrückt die normale Ausgabe und /regex/pbedeutet "alle Zeilen drucken, mit denen der reguläre Ausdruck übereinstimmt".

  • Wie kann ich nur Zeilen drucken, deren 2. Feld nicht ist foo?

    sed '/^[^:]*:foo:/d' file

    Die logische Umkehrung des Obigen. Hier /regex/dbedeutet das "alle Zeilen löschen, mit denen der reguläre Ausdruck übereinstimmt".

  • Wie kann ich nur Zeilen drucken, deren 2. Feld übereinstimmt foo?

    sed -n '/^[^:]*:[^:]*foo/p' file
  • Wie kann ich nur Zeilen drucken, deren 2. Feld nicht übereinstimmt foo?

    sed '/^[^:]*:[^:]*foo/d' file
  • Wie kann ich das 2. Feld in ändern foo?

    sed 's/\([^:]*:\)[^:]*/\1foo/' file 

    Oder, da die sedSubstitution ein Auftreten von Mustern durch ihre Wiederholung mit einem einfachen numerischen Flag direkt ansprechen kann:

    sed 's/[^:]*/foo/2' file
terdon
quelle
(1) Gibt es ein Standardwerkzeug für Gartenvielfalt * nix, das eingebettete Trennzeichen in Feldern versteht, z Capt. Kirk, Mr. Spock, "Dr. McCoy, MD", Scotty. (2) Ich bin beunruhigt über die Tatsache, dass (i) → 2 und (ii) → 1 , aber ich wollte nichts so Wichtiges ändern. (3) Wenn dies als kanonische Frage nominiert wird, sollte es dann nicht auf einige gründliche und maßgebliche Verweise auf reguläre Ausdrücke verweisen (z. B. Wikipedia , Regular-Expressions.info , RegexPlanet usw.)?
G-Man sagt "Reinstate Monica"
@ G-Man 1) nicht so weit ich weiß 2) fair genug, behoben 3) Ich wollte nur eine einfache Einführung in die beiden Hauptansätze für den Umgang mit Feldern schreiben. Ich möchte nicht zu sehr ins Detail gehen und es ist sicherlich nicht darauf ausgerichtet, eine Referenz auf reguläre Ausdrücke zu sein. Danke übrigens, dass
du
Falls es Sie interessiert: Funktioniert s/:[^:]*/:foo/auch., Aber das Backref ist es wahrscheinlich auch wert, in einen kanonischen Beitrag über Regexp aufgenommen zu werden.
Mikeserv
@ G-Man - sedkann die eingebetteten Trennzeichen ausführen, und wirklich jedes voll BRE-fähige Tool kann dies auch. Mit dieser Liste gaben Sie, tun: sed 's/[^,"]*\("[^"]*\)\{0,1\}[^,]*,//;s///2' <listgedruckt wird Mr. Spock, Scotty (mit einem führenden Leerzeichen) , weil der erste Wechsel das erste Feld entfernt, und die zweiten entfernt Knochen.
Mikeserv
Sie sagen Unlike awk, perl can't use regular expressions as field delimiters. - dies gilt nur, wenn Sie die -aautomatische Aufteilung verwenden. Sie können sich trotzdem benutzen, split()wenn Sie wollen. zBperl -p -e 'my @F = split /regexp/ ; print if $F[2] =~ /foo/'
cas