Kombiniere zwei Dateien mit awk

9

File1.txt

item1   carA
item2   carB
item3   carC
item4   platD
item5   carE

File2.txt

carA  platA
carB  platB
carC  platC
carE  platE

Gewünschte Ausgabe:

item1   platA
item2   platB
item3   platC
item4   platD
item5   platE

Wie kann ich es tun?

Pawana
quelle

Antworten:

11

Die folgende Antwort basiert auf ähnlichen Fragen und Antworten in SO mit einigen relevanten Änderungen:

$ awk 'FNR==NR {dict[$1]=$2; next} {$2=($2 in dict) ? dict[$2] : $2}1' file2.txt file1.txt 
item1 platA
item2 platB
item3 platC
item4 platD
item5 platE

Die Idee ist, eine Hash-Map mit Index zu erstellen und als Wörterbuch zu verwenden.

Für die zweite Frage, die Sie in Ihrem Kommentar gestellt haben ( was sollte geändert werden, wenn die zweite Spalte von file1.txtdie sechste Spalte ist ):

Wenn die Eingabedatei wie folgt aussehen wird file1b.txt:

item1 A5 B C D carA
item2 A4 1 2 3 carB
item3 A3 2 3 4 carC
item4 A2 4 5 6 platD
item5 A1 7 8 9 carE

Der folgende Befehl wird es tun:

$ awk 'FNR==NR {dict[$1]=$2; next} {$2=($6 in dict) ? dict[$6] : $6;$3="";$4="";$5="";$6=""}1' file2.txt file1b.txt 
item1 platA    
item2 platB    
item3 platC    
item4 platD    
item5 platE    
Yaron
quelle
1
@pawana - Ich habe meine Antwort aktualisiert, um auch Ihre zweite Frage im Kommentar zu lösen. Wenn ich Ihre Frage beantwortet habe, akzeptieren Sie sie bitte .
Yaron
6

Ich weiß, dass Sie gesagt haben awk, aber es gibt einen joinBefehl für diesen Zweck ...

{
  join -o 1.1,2.2 -1 2 -2 1 <(sort -k 2 File1.txt) <(sort -k 1 File2.txt)     
  join -v 1 -o 1.1,1.2 -1 2 -2 1 <(sort -k 2 File1.txt) <(sort -k 1 File2.txt) 
} | sort -k 1

Mit dem ersten joinBefehl würde es ausreichen, wenn diese Zeile nicht wäre:

item4   platD

Der Befehl lautet im Wesentlichen: Join basierend auf der zweiten Spalte der ersten Datei ( -1 2) und der ersten Spalte der zweiten Datei ( -2 1) und Ausgabe der ersten Spalte der ersten Datei und der zweiten Spalte der zweiten Datei ( -o 1.1,2.2). Das zeigt nur die gepaarten Linien. Der zweite Join-Befehl sagt fast dasselbe aus, zeigt jedoch die Zeilen aus der ersten Datei an, die nicht gepaart werden konnten ( -v 1), und gibt die erste Spalte der ersten Datei und die zweite Spalte der ersten Datei ( -o 1.1,1.2) aus. Dann sortieren wir die Ausgabe von beiden kombiniert. sort -k 1bedeutet sortieren nach der ersten Spalte und sort -k 2bedeutet sortieren nach der zweiten Spalte . Es ist wichtig, die Dateien nach der Join-Spalte zu sortieren, bevor Sie sie an übergeben join.

Jetzt habe ich die Sortierung zweimal geschrieben, weil ich meine Verzeichnisse nicht gerne mit Dateien verunreinige, wenn ich helfen kann. Wie David Foerster jedoch sagte, möchten Sie je nach Größe der Dateien die Dateien möglicherweise sortieren und zuerst speichern, um nicht zweimal warten zu müssen. Um eine Vorstellung von den Größen zu bekommen, ist hier die Zeit, die benötigt wird, um 1 Million und 10 Millionen Zeilen auf meinem Computer zu sortieren:

$ ruby -e '(1..1000000).each {|i| puts "item#{i}   plat#{i}"}' | shuf > 1million.txt 
$ ruby -e '(1..10000000).each {|i| puts "item#{i}   plat#{i}"}' | shuf > 10million.txt 
$ head 10million.txt 
item530284   plat530284
item7946579   plat7946579
item1521735   plat1521735
item9762844   plat9762844
item2289811   plat2289811
item6878181   plat6878181
item7957075   plat7957075
item2527811   plat2527811
item5940907   plat5940907
item3289494   plat3289494
$ TIMEFORMAT=%E
$ time sort 1million.txt >/dev/null
1.547
$ time sort 10million.txt >/dev/null
19.187

Das sind 1,5 Sekunden für 1 Million Zeilen und 19 Sekunden für 10 Millionen Zeilen.

JoL
quelle
In diesem Fall ist es besser, die sortierten Eingabedaten in (temporären) Zwischendateien zu speichern, da das Sortieren für nicht trivial große Datensätze ziemlich lange dauert. Ansonsten +1.
David Foerster
@ David Es ist ein guter Punkt. Persönlich mag ich es nicht, Zwischendateien erstellen zu müssen, aber ich bin auch ungeduldig mit lang laufenden Prozessen. Ich fragte mich, was "trivial groß" sein würde, und so machte ich einen kleinen Benchmark und fügte ihn der Antwort zusammen mit Ihrem Vorschlag hinzu.
JoL
Das Sortieren von 1 Million Datensätzen ist auf einigermaßen modernen Desktop-Computern schnell genug. Mit 2 weiteren 3 Größenordnungen werden mehr Dinge interessant. In jedem Fall ist die verstrichene (Echtzeit-) Zeit ( %Eim Zeitformat) weniger interessant, um die Rechenleistung zu messen. Die CPU-Zeit im Benutzermodus ( %Uoder einfach eine nicht gesetzte TIMEFORMATVariable) wäre viel aussagekräftiger.
David Foerster
@ David Ich bin nicht wirklich vertraut mit den Anwendungsfällen für die verschiedenen Zeiten. Warum ist es interessanter? Die verstrichene Zeit stimmt mit der Zeit überein, auf die ich tatsächlich warte. Für den 1,5-Sekunden-Befehl erhalte ich 4,5 Sekunden mit %U.
JoL
1
Die verstrichene Zeit wird durch die Wartezeit auf andere Aufgaben beeinflusst, die auf demselben System ausgeführt werden, und durch das Blockieren von E / A-Anforderungen. (Benutzer-) CPU-Zeit ist nicht. Wenn man die Geschwindigkeit rechnerisch gebundener Algorithmen vergleicht, möchte man normalerweise E / A ignorieren und Messfehler aufgrund anderer Hintergrundaufgaben vermeiden. Die wichtige Frage lautet: "Wie viel Berechnung benötigt dieser Algorithmus für diesen Datensatz?" statt "Wie viel Zeit hat mein Computer für alle seine Aufgaben aufgewendet, während er auf den Abschluss dieser Berechnung gewartet hat?"
David Foerster