
Ich möchte einen diff
auf 2 Dateien ausführen und ihn beim ersten Unterschied beenden. Ich verlange diff
natürlich nicht, dass der Befehl über ausgeführt wird, aber ich verlange, dass der eigentliche Befehl beendet wird, sobald der erste Unterschied gefunden und gemeldet wurde. Ich verwende einigesehrgroße Dateien und erwarte eine perfekte Übereinstimmung, möchte aber trotzdem wissen, was der Unterschied war, falls einer gefunden wird, also sind diff -q
, diff ... |head -1
, und cmp
nicht gut. Und da die Dateien sehr groß sind, wäre etwas nett, das den Speicher nicht erschöpft. Obwohl es für mein aktuelles Problem nicht notwendig ist, gibt es Bonuspunkte für Lösungen, die für die ersten (vom Benutzer angegebenen) n Unterschiede funktionieren, und für solche, die Leerzeichenunterschiede ignorieren können.
Antwort1
cmp
bleibt beim ersten Unterschied stehen:
% cat foo
foo
bar
baz
---
foo
bar
baz
% cat bar
foo
bar
baz
---
foo+
bar+
baz+
% cmp foo bar
foo bar differ: byte 20, line 5
%
Sie könnten ein Skript darum herum packen, um die verschiedenen Zeilen auszudrucken:
#! /bin/bash
line=$(cmp "$1" "$2" | awk '{print $NF}')
if [ ! -z $line ]; then
awk -v file="$1" -v line=$line 'NR==line{print "In file "file": "$0; exit}' "$1"
awk -v file="$2" -v line=$line 'NR==line{print "In file "file": "$0; exit}' "$2"
fi
% ./script.sh foo bar
In file foo: foo
In file bar: foo+
Ein Teil des Aufwands wird nun auf die AWK-Befehle verlagert, es sollte jedoch deutlich schneller sein, als wenn beide Dateien vollständig überprüft würden.
Antwort2
Ich habe dies mit den trivialen Fällen getestet, überlasse den Praxistest aber Ihnen:
$ cat f1
l1
l21 l22 l23 l24
l3
l4x
l5
$ cat f2
l1
l21 l22 l23
l3
l4y
l5
$ cat awkdiff.awk
BEGIN {
maxdiff = 5
ignoreemptylines = 1
whitespaceaware = 1
if (whitespaceaware) {
emptypattern = "^[[:space:]]*$"
} else {
emptypattern = "^$"
FS=""
}
f1 = ARGV[1]
f2 = ARGV[2]
rc1=rc2=1
while( (rc1>0 && rc2>0 && diff<maxdiff) ) {
rc1 = getline l1 < f1 ; ++nr1
rc2 = getline l2 < f2 ; ++nr2
if (ignoreemptylines) {
while ( l1 ~ emptypattern && rc1>0) {
rc1 = getline l1 < f1 ; ++nr1
}
while ( l2 ~ emptypattern && rc2>0) {
rc2 = getline l2 < f2 ; ++nr2
}
}
if ( rc1>0 && rc2>0) {
nf1 = split( l1, a1)
nf2 = split( l2, a2)
if ( nf1 <= nf2) {
nfmin = nf1
} else {
nfmin = nf2
}
founddiff = 0
for (i=1; i<=nfmin; ++i) {
if ( a2[i]"" != a1[i]"") {
printf "%d:%d:{%s} != %d:%d:{%s}\n", \
nr1, nf1, a1[i], nr2, nf2, a2[i]
founddiff=1
++diff
break
}
}
if ( !founddiff && nf1 != nf2) {
if ( nf1 > nf2)
printf "%d:%d:{%s} != %d:EOL\n", nr1, nfmin+1, a1[nfmin+1], nr2
else
printf "%d:EOL != %d:%d:{%s}\n", nr1, nr2, nfmin+1, a2[nfmin+1]
++diff
}
} else {
if ( rc1 == -1 && rc2 == -1) {
print "IO error"
} else if ( rc1 == 1 && rc2 == 0) {
print "%d:%s != EOL\n", nr1, l1
} else if ( rc1 == 0 && rc2 == 1) {
printf "EOL != %d:%s\n", nr2, l2
}
}
}
}
$ awk -f awkdiff.awk /tmp/f1 /tmp/f2
2:4:{l24} != 2:EOL
6:1:{l4x} != 5:1:{l4y}
maxdiff = N: legt die maximale Anzahl von Unterschieden fest, bei der der Vergleich beendet werden soll
ignoreemptylines = 1|0: gibt an, ob leere Zeilen beim Vergleichen ignoriert werden sollen
whitespaceaware = 1|0: gibt an, ob der Vergleich wortweise (unter der Annahme, dass aufeinanderfolgende Leerzeichen gleich sind) oder zeilenweise erfolgen soll