PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [BASH] Unterschiedliche Text-Dateien vergleichen, nach identischem String suchen.



mrt181
06-02-2009, 11:08
Hallo,

ich möchte Daten zeilenweise Vergleichen. In den zu vergleichenden Dateien stehen die Informationen auch zeilenweise drin aber die Dateien sind völlig unterschiedlich aufgebaut.

Beispiel:
1. Datei:
TESTTESTTEST#000#000555333000000101 2008-01-01

2. Datei:
101555333 WKS21349342290 20080117

Ich möchte aber wissen ob die rot markierten strings aus der 1. Datei, in der 2. Datei vorkommen und zwar so wie ich es im Beispiel angegeben habe, d.h. das 555333000000101 aus der 1. Datei in der zweiten Datei als 101555333 vorkommt. Die Daten aus der 1. Datei, die nicht in der 2. vorkommen, will ich unverändert in ein neue Datei schreiben.

Jemand eine Idee, meine Kenntnisse beschränken sich auf Grundlagen-Bash. Die Daten aus beiden Dateien abgreifen, sortieren etc. ist kein Problem aber das Vergleichen der unveränderten Dateien und das abgreifen der Differenz will mir nicht gelingen.

Ich denke ich muss die Zeilen aus der 2. Datei einlesen, daraus dann die ersten drei und die nächsten 6 Zeichen jeder Zeile jeweils mit den 33 bis 34 und 21 bis 26 Zeichen jeder Zeile aller 1. Dateien vergleichen, bloss wie mach ich dass???

jan61
06-02-2009, 20:06
Moin,


...
1. Datei:
TESTTESTTEST#000#000555333000000101 2008-01-01

2. Datei:
101555333 WKS21349342290 20080117

...Die Daten aus der 1. Datei, die nicht in der 2. vorkommen, will ich unverändert in ein neue Datei schreiben...

Du solltest Dir dafür ein paar Hilfsdateien aufbauen, damit Du eine Grundlage zum Vergleich hast. Ich nehme mal an, dass die Dateien groß sein können, also mache ich es ohne zeilenweises Ausführen externer Kommandos:


jan@jack:~/tmp/dateivergleich> cat datei1
TESTTESTTEST#000#000555334000000101 2008-01-01
TESTTESTTEST#000#000555337000000101 2008-01-01
TESTTESTTEST#000#000555335000000101 2008-01-01
TESTTESTTEST#000#000555333000000101 2008-01-01
TESTTESTTEST#000#000555336000000101 2008-01-01
TESTTESTTEST#000#000555338000000101 2008-01-01
jan@jack:~/tmp/dateivergleich> cat datei2
101555335 WKS21349342290 20080117
101555333 WKS21349342290 20080117
101555337 WKS21349342290 20080117
jan@jack:~/tmp/dateivergleich> awk ' { printf "%s%s 1 %s\n", substr($0,33,3), substr($0,21,6), $0; } ' datei1 >datei1.cmp
jan@jack:~/tmp/dateivergleich> cat datei1.cmp
101555334 1 TESTTESTTEST#000#000555334000000101 2008-01-01
101555337 1 TESTTESTTEST#000#000555337000000101 2008-01-01
101555335 1 TESTTESTTEST#000#000555335000000101 2008-01-01
101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01
101555336 1 TESTTESTTEST#000#000555336000000101 2008-01-01
101555338 1 TESTTESTTEST#000#000555338000000101 2008-01-01
jan@jack:~/tmp/dateivergleich> awk ' { printf "%s 2 %s\n", $1, $0; } ' datei2 >datei2.cmp
jan@jack:~/tmp/dateivergleich> cat datei2.cmp
101555335 2 101555335 WKS21349342290 20080117
101555333 2 101555333 WKS21349342290 20080117
101555337 2 101555337 WKS21349342290 20080117
jan@jack:~/tmp/dateivergleich> sort -k1,2 datei1.cmp datei2.cmp
101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01
101555333 2 101555333 WKS21349342290 20080117
101555334 1 TESTTESTTEST#000#000555334000000101 2008-01-01
101555335 1 TESTTESTTEST#000#000555335000000101 2008-01-01
101555335 2 101555335 WKS21349342290 20080117
101555336 1 TESTTESTTEST#000#000555336000000101 2008-01-01
101555337 1 TESTTESTTEST#000#000555337000000101 2008-01-01
101555337 2 101555337 WKS21349342290 20080117
101555338 1 TESTTESTTEST#000#000555338000000101 2008-01-01
jan@jack:~/tmp/dateivergleich> sort -k1,2 datei1.cmp datei2.cmp | awk ' BEGIN { prev_no = 0; }
$2 == 1 { if (prev_no == 1) print prev_content; }
{ prev_no = $2; prev_content = $0; }
END { if ($2 == 1) print $0; } ' | cut -f3- -d" "
TESTTESTTEST#000#000555334000000101 2008-01-01
TESTTESTTEST#000#000555336000000101 2008-01-01
TESTTESTTEST#000#000555338000000101 2008-01-01
Was läuft da ab?
1. Ich habe mir mal 2 kleine Beispieldateien gebastelt.
2. Die nächsten beiden awk-Aufrufe ezeugen jeweils eine .cmp-Datei, in der am Zeilenanfang der zu vergleichende String und die Dateinummer eingefügt wird.
3. Jetzt kann ich nach den Feldern 1 und 2 sortieren (sort -k1,2) und erhalte die oben gezeigt Ausgabe. Wenn jetzt also im Feld 2 eine "1" steht und in der vorigen Zeile auch eine "1" stand, dann weiß ich dass es für den Suchbegriff in der vorigen Zeile keine Entsprechung in datei2 gab.
4. Mit diesem Wissen kann ich den dritten awk ablaufen lassen, am Ende muss die letzte Zeile noch ausgegeben werden, wenn sie aus datei1 stammt.
5. Zum Schluss pipe ich die Ausgabe noch an einen cut, der mir nur den ursprünglichen Zeileninhalt (ab Feld 3) ausgibt (könnte man auch im awk direkt machen, aber das ist etwas umständlicher).

Die Lösung kommt mit insgesamt 5 Kommandoaufrufen aus und speichert keine Dateiinhalte im Hauptspeicher, hat aber eine Einschränkung: Die Zeilen werden nicht in der Originalreihenfolge aus datei1 ausgegeben. Das kann man erreichen, indem man in datei1.cmp zusätzlich die Zeilennummer als 3. Feld speichert und dann vor dem cut noch eine Sortierung nach dieser Zeilennummer macht. Der 1. awk müsste dann so aussehen:

jan@jack:~/tmp/dateivergleich> awk ' { printf "%s%s 1 %d %s\n", substr($0,33,3), substr($0,21,6),NR, $0; } ' datei1 >datei1.cmp
jan@jack:~/tmp/dateivergleich> cat datei1.cmp
101555334 1 1 TESTTESTTEST#000#000555334000000101 2008-01-01
101555337 1 2 TESTTESTTEST#000#000555337000000101 2008-01-01
101555335 1 3 TESTTESTTEST#000#000555335000000101 2008-01-01
101555333 1 4 TESTTESTTEST#000#000555333000000101 2008-01-01
101555336 1 5 TESTTESTTEST#000#000555336000000101 2008-01-01
101555338 1 6 TESTTESTTEST#000#000555338000000101 2008-01-01
In die letzte Befehlsfolge muss dann noch ein zusätzlicher sort:

jan@jack:~/tmp/dateivergleich> sort -k1,2 datei1.cmp datei2.cmp | awk ' BEGIN { prev_no = 0; }
$2 == 1 { if (prev_no == 1) print prev_content; }
{ prev_no = $2; prev_content = $0; }
END { if ($2 == 1) print $0; } ' | sort -n -k3 | cut -f4- -d" "
Im Beispiel ist kein Unterschied zu sehen (die Reihenfolge stimmte leider schon im Original), ich habs aber nochmal kurz getestet, funktioniert ;-)

Jan

EDIT: Der vor dem abschließenden awk aufgerufene sort benötigt die Option "-k1,2" nicht zwingend, das Ergebnis wird davon nicht beeinflusst, da die ersten beiden Felder eh die Reihenfolge bestimmen.

mrt181
08-02-2009, 09:48
wow, vielen dank. awk ist also eine lösung. awk kann ich nicht, muss ich wohl ändern :). werde das mal am montag versuchen.

Hab ich jetzt getestet, funktioniert super

Wie lass ich das ganz am besten über mehrere unterschiedliche Verzeichnisse laufen?


for i in $(ls path/Datei_1*.txt); do 1.kommando ; done
for i in $(ls path/Datei_2*.txt); do 2.kommando ; done
3.kommando > Final.txt

jan61
09-02-2009, 19:58
Moin,

wenn sich die Dateien in unterschiedlichen Verzeichnissen befinden, läuft es im Prinzip identisch ab - Du musst ja irgendwie wissen, welche der Dateien Du paarweise miteinander vergleichen willst (oder sollen es alle auf einmal sein?). Im ersten Fall würde ich eher nur über ein Verzeichnis iterieren und mir jeweils die passende Datei im anderen Verzeichnis holen - das spart Platz für temp. Dateien. Im 2. Fall würde ich eher alles in jeweils eine Datei packen und mit der weiterarbeiten. Du kannst dem awk ja (wie fast allen anderen Unix-Kommandos) soviele Dateien auf einen Schlag vorwerfen, wie die Puffer der Kommandozeile raffen (und mit einem kleinen Trick beliebig viele):

# nur soviele Dateien, wie der Argumentpuffer fasst
awk '...' dir1/file1*.txt >file1.cmp
# beliebig viele
find dir1 -mindepth 1 -maxdepth 1 -name 'file1*.txt' -print | xargs awk '...' >file1.cmpJan

mrt181
11-02-2009, 10:34
jetzt hat sich noch etwas weiters ergeben. ich muss insgesamt drei Dateien vergleichen.

Ich bin so wie bisher vorgegangen. Aus dem Vergleich der ersten 2 Dateien habe ich meine Informationen herausgeholt. Jetzt vergleiche ich diese Ergbniss-Datei mit der 3. Datei.


awk '{printf "%s%s 1 %s\n", substr($0,29,3), substr($0,18,5), $0;}' Ergebnis-Datei.txt >> Ergebnis.cmp
awk '{printf "%s 2 %s\n", substr($0,18,9), $0;}' datei3.txt >> datei3.cmp
sort -k1,2 Ergebnis.cmp datei3.cmp > half-final.cmp

Das funktioniert soweit. Ich brauch aber aus der half-final.cmp alle Zeilen mit der 1 und einer darauf folgenden 2.

Beispiel

101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01 A
101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01 B
101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01 C
101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01 D
101555333 2 3DATEI3DATEI# 000101555333ABC
101555335 1 TESTTESTTEST#000#000555335000000101 2008-01-01 A
101555335 1 TESTTESTTEST#000#000555335000000101 2008-01-01 B
101555335 2 3DATEI3DATEI# 000101555335ABC
Momentan habe ich es aber so

101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01 A
101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01 B
101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01 C
101555333 1 TESTTESTTEST#000#000555333000000101 2008-01-01 D
101555333 2 3DATEI3DATEI# 000101555333ABC
101555334 2 3DATEI3DATEI# 000101555334ABC
101555335 1 TESTTESTTEST#000#000555335000000101 2008-01-01 A
101555335 1 TESTTESTTEST#000#000555335000000101 2008-01-01 B
101555335 2 3DATEI3DATEI# 000101555335ABC

jan61
11-02-2009, 19:59
Moin,

nimm den letzten awk aus meinem ersten Posting und erweitere ihn entsprechend (ungetestet):
awk ' BEGIN { prev_no = 0; }
$2 == 2 { if (prev_no == 1) print prev_content; print $0; }
{ prev_no = $2; prev_content = $0; } ' half-final.cmpJan