Entfernen Sie mit Ausnahme der ersten Zeile zusätzliche Kopfzeilen aus der Datei

18

Ich habe eine Datei, die wie dieses Spielzeugbeispiel aussieht. Meine eigentliche Datei enthält 4 Millionen Zeilen, von denen etwa 10 gelöscht werden müssen.

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
ID  Data1  Data2
4    100    100
ID  Data1  Data2
5    200    200

Ich möchte die Zeilen löschen, die wie die Überschrift aussehen, mit Ausnahme der ersten Zeile.

Endgültige Datei:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200

Wie kann ich das machen?

Gaius Augustus
quelle

Antworten:

26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. Greifen Sie die Kopfzeile aus der Eingabedatei in eine Variable
  2. Den Header ausdrucken
  3. Verarbeiten Sie die Datei mit grep, um Zeilen auszulassen, die mit dem Header übereinstimmen
  4. Erfassen Sie die Ausgabe der beiden oben genannten Schritte in der Ausgabedatei
Jeff Schaller
quelle
2
oder vielleicht{ IFS= read -r head; printf '%s\n' "$head"; grep -vF "$head" ; } <file
iruvar
Beides gute Ergänzungen. Vielen Dank an don_crissti für den indirekten Hinweis, dass posix kürzlich die -1-Syntax von head zugunsten von -n 1 entfernt hat.
Jeff Schaller
3
@ JeffSchaller, vor kurzem wie in 12 Jahren. Und head -1wurde vor Jahrzehnten überholt.
Stéphane Chazelas
36

Sie können verwenden

sed '2,${/ID/d;}'

Dadurch werden Zeilen mit der ID ab Zeile 2 gelöscht.

bkmoney
quelle
3
nett; oder genauer gesagt mit der Mustererkennung sed '2,${/^ID Data1 Data2$/d;}' file(natürlich mit der richtigen Anzahl von Leerzeichen zwischen den Spalten)
Jeff Schaller
Hm ich dachte du könntest das Semikolon für nur 1 Befehl weglassen, aber ok.
bkmoney
Nicht w / sane seds, nein.
mikeserv
aaaand -i für den In-Place-Edit-Gewinn.
user2066657
4
Odersed '1!{/ID/d;}'
Stéphane Chazelas
10

Für diejenigen, die keine geschweiften Klammern mögen

sed -e '1n' -e '/^ID/d'
  • nbedeutet passZeile Nr.1
  • d Löschen Sie alle übereinstimmenden Zeilen, die mit "" beginnen ^ID
Costas
quelle
5
Dies kann auch auf sed '1n;/^ID/d'Dateiname abgekürzt werden. Nur ein Vorschlag
Valentin Bajrami
Beachten Sie, dass hierdurch auch Zeilen gedruckt werden, IDfoodie nicht mit der Kopfzeile identisch sind (in diesem Fall ist es unwahrscheinlich, dass dies einen Unterschied macht, aber Sie wissen es nie).
Terdon
6

Hier ist eine lustige. Sie können seddirekt verwenden, um alle Kopien der ersten Zeile zu entfernen und alles andere (einschließlich der ersten Zeile selbst) an Ort und Stelle zu lassen.

sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//' input

1{h;n;}Setzt die erste Zeile in den Haltebereich, druckt sie aus und liest die nächste Zeile ein. Der Rest der sedBefehle für die erste Zeile wird übersprungen . (Es überspringt auch diesen ersten 1Test für die zweite Zeile , aber das spielt keine Rolle, da dieser Test nicht auf die zweite Zeile angewendet worden wäre.)

G Fügt eine neue Zeile, gefolgt vom Inhalt des Haltebereichs, an den Musterbereich an.

/^\(.*\)\n\1$/dlöscht den Inhalt des Musterbereichs (springt also zur nächsten Zeile), wenn der Teil nach der neuen Zeile (dh was aus dem Haltebereich angehängt wurde) genau mit dem Teil vor der neuen Zeile übereinstimmt. Hier werden Zeilen gelöscht, die den Header duplizieren.

s/\n.*$//Löscht den vom GBefehl hinzugefügten Textabschnitt , sodass nur die Textzeile aus der Datei gedruckt wird.

Da Preguläre Ausdrücke jedoch teuer sind, wäre ein etwas schnellerer Ansatz, dieselbe Bedingung (negiert) zu verwenden und bis zum Zeilenumbruch zu drucken, wenn der Teil nach dem Zeilenumbruch (dh was aus dem Laderaum angehängt wurde) nicht genau mit dem Teil übereinstimmt vor dem Zeilenumbruch und dann unbedingt den Musterraum löschen:

sed '1{h;n;};G;/^\(.*\)\n\1$/!P;d' input

Ausgabe, wenn Ihre Eingabe gegeben ist:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200
Platzhalter
quelle
@don_crissti, interessante Ergänzung; Vielen Dank! Ich würde mich wahrscheinlich für das längere aber gleichwertige entscheiden sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; irgendwie fällt mir das lesen leichter. :)
Wildcard
Ebenfalls im Zusammenhang: unix.stackexchange.com/a/417736/135943
Wildcard
5

Hier sind einige weitere Optionen, bei denen Sie die erste Zeile nicht im Voraus kennen müssen:

perl -ne 'print unless $_ eq $k; $k=$_ if $.==1; 

Das -nFlag weist Perl an, die Eingabedatei zu durchlaufen und jede Zeile als zu speichern $_. Der $k=$_ if $.==1;speichert die erste Zeile ( $.ist die Zeilennummer, gilt also $.==1nur für die 1. Zeile) als $k. Das print unless $k eq $_druckt die aktuelle Zeile aus, wenn sie nicht mit der gespeicherten übereinstimmt $k.

Alternativ dazu dasselbe in awk:

awk '$0!=x;(NR==1){x=$0}' file 

Hier testen wir, ob die aktuelle Zeile mit der in der Variablen gespeicherten übereinstimmt x. Wenn der Test $0!=xmit true bewertet wird (wenn die aktuelle Zeile $0nicht mit true übereinstimmt x), wird die Zeile gedruckt, da die Standardaktion für awk für true-Ausdrücke das Drucken ist. Die erste Zeile ( NR==1) wird gespeichert als x. Da dies erfolgt, nachdem geprüft wurde, ob die aktuelle Zeile übereinstimmt x, wird auch die erste Zeile gedruckt.

terdon
quelle
Ich mag es nicht, die Idee der ersten Zeile zu kennen, da es ein verallgemeinertes Skript für Ihre Toolbox ist.
Mark Stewart
1
Diese awk-Methode erstellt einen leeren / falschen Array-Eintrag pro Zeile. für 4M-Leitungen, wenn alle verschieden (nicht klar von Q) und ziemlich kurz (scheint so), ist dies wahrscheinlich in Ordnung, aber wenn es viel mehr oder längere Leitungen gibt, könnte dies verprügeln oder sterben. !($0 in a)testet ohne zu erstellen und vermeidet dies, oder awk kann die gleiche Logik wie für perl '$0!=x; NR==1{x=$0}''NR==1{x=$0;print} $0!=x'
ausführen
1
@ Dave_Thompson_085 Wo wird ein Array pro Zeile erstellt? Du meinst !a[$0]? Warum würde das einen Eintrag in erstellen a?
Terdon
1
Denn so funktioniert awk. Siehe gnu.org/software/gawk/manual/html_node/… besonders den "HINWEIS".
Dave_thompson_085
1
@ Dave_Thompson_085 Nun, ich werde verdammt sein! Danke, das war mir nicht bewusst. Jetzt behoben.
Terdon
4

AWK ist auch für diesen Zweck ein recht anständiges Werkzeug. Hier ist ein Codebeispiel:

$ awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt | head -n 10                                
ID  Data1  Data2
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100

Brechen :

  • NR == 1 {print} sagt uns, dass wir die erste Zeile der Textdatei drucken sollen
  • NR != 1 && $0!~/ID Data1 Data2/ Der logische Operator &&weist AWK an, eine Zeile zu drucken, die ungleich 1 ist und keine enthält ID Data1 Data2. Beachten Sie das Fehlen eines {print}Teils; Wenn in awk eine Testbedingung als wahr bewertet wird, wird angenommen, dass die Zeile gedruckt wird.
  • | head -n 10ist nur eine winzige Erweiterung, um die Ausgabe auf die ersten 10 Zeilen zu beschränken. Für das AWKTeil selbst nicht relevant , wird nur zu Demonstrationszwecken verwendet.

Wenn Sie dies in einer Datei wünschen, leiten Sie die Ausgabe des Befehls um, indem Sie > newFile.txtam Ende des Befehls Folgendes anfügen:

awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > newFile.txt

Wie hält es aus? Ziemlich gut eigentlich:

$ time awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > /dev/null                            
    0m3.60s real     0m3.53s user     0m0.06s system

Randnotiz

Die generierte Beispieldatei wurde erstellt, um eine Schleife zwischen 1 und 1 Million zu erstellen und die ersten vier Zeilen Ihrer Datei zu drucken (also 4 Zeilen mal 1 Million entspricht 4 Millionen Zeilen). Dies dauerte übrigens 0,09 Sekunden.

awk 'BEGIN{ for(i=1;i<=1000000;i++) printf("ID  Data1  Data2\n1    100    100\n     100    200\n3    200    100\n");  }' > rmLines.txt
Sergiy Kolodyazhnyy
quelle
Beachten Sie, dass hierdurch auch Zeilen gedruckt werden, ID Data1 Data2 foodie nicht mit der Kopfzeile identisch sind (in diesem Fall ist es unwahrscheinlich, dass dies einen Unterschied macht, aber Sie wissen es nie).
Terdon
@terdon ja genau richtig. OP spezifizierte jedoch nur ein Muster, das sie entfernen wollten, und sein Beispiel scheint dies zu unterstützen
Sergiy Kolodyazhnyy
3

Awk, passt sich automatisch an jede Überschrift an:

awk '( FNR == 1) {header=$0;print $0;}
     ( FNR > 1) && ($0 != header) { print $0;}'  file1  file2 ....

dh in der ersten Zeile wird der Header abgerufen und gedruckt, und die nachfolgende Zeile DIFFERENT aus diesem Header wird gedruckt.

FNR = Anzahl der Datensätze in der aktuellen Datei, so dass Sie mehrere Dateien haben können, und es wird in jeder von ihnen dasselbe tun.

Olivier Dulac
quelle
2

Der Vollständigkeit halber gab Perl-Lösung IMO etwas eleganter als @terdon:

perl -i -p -e 's/^ID.*$//s if $. > 1' file
KWubbufetowicz
quelle
1
Ah, aber der springende Punkt war, die Notwendigkeit zu vermeiden, das Muster zu spezifizieren und es stattdessen aus der ersten Zeile zu lesen. Ihr Ansatz löscht einfach jede Zeile, die mit beginnt ID. Sie können nicht garantieren, dass dadurch nicht die Zeilen gelöscht werden, die beibehalten werden sollen. Da Sie Eleganz erzogen haben, gist es sinnlos, wenn Sie verwenden^ und verwendest $. In der Tat sind alle Ihre Optionen m///hier außer nutzlos s; Sie aktivieren Funktionen, die Sie nicht verwenden. So ist das $, s/^ID.*//swürde das gleiche tun.
Terdon
@terdon, fair genug. Dein ist viel universeller!
KWubbufetowicz
2

Nur um die Frage ein wenig zurückzudrängen ... es sieht so aus, als ob Ihre Eingabe selbst das Ergebnis des Zusammenpassens mehrerer TSV-Dateien ist. Wenn Sie einen Schritt in Ihrer Verarbeitungspipeline sichern können (wenn Sie das besitzen oder mit den Verantwortlichen sprechen können), können Sie die Daten zunächst mit einem Tool verketten, das den Header berücksichtigt, und so das Problem der Notwendigkeit beseitigen Entfernen Sie zusätzliche Kopfzeilen.

Zum Beispiel mit Miller :

$ cat f1.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
$ cat f2.tsv
ID  Data1 Data2
4 100 100
$ cat f3.tsv
ID  Data1 Data2
5 200 200

$ cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
ID  Data1 Data2
4 100 100
ID  Data1 Data2
5 200 200

$ mlr --tsvlite cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
4 100 100
5 200 200
John Kerl
quelle
1
Vielen Dank, dass Sie diesen Leckerbissen hinzugefügt haben. Dies wird in Zukunft äußerst nützlich sein, da die meisten meiner Pipelines das Zusammenführen und Zusammenführen von Dateien aus einzelnen Beispielen erfordern.
Gaius Augustus