Как различать большие файлы в Linux
Я получаю diff: memory exhausted
ошибка при попытке различить два файла размером 27 ГБ, которые в основном похожи на Linux-систему с CentOS 5 и 4 ГБ ОЗУ. Кажется, это известная проблема.
Я ожидаю, что найдется альтернатива для такой важной утилиты, но я не могу ее найти. Я полагаю, что решение будет использовать временные файлы, а не память для хранения необходимой информации.
- Я пытался использовать
rdiff
а такжеxdelta
, но они лучше показывают изменения между двумя файлами, как патч, и не очень полезны для проверки различий между двумя файлами. - Пробовал VBinDiff, но это визуальный инструмент, который лучше сравнивать двоичные файлы. Мне нужно что-то, что может передать различия в STDOUT, как обычный
diff
, - Есть много других утилит, таких как
vimdiff
это работает только с небольшими файлами. - Я также читал о Солярисе
bdiff
но я не смог найти порт для Linux.
Любые идеи, кроме разделения файла на более мелкие части? У меня есть 40 таких файлов, поэтому я стараюсь не разбивать их.
6 ответов
cmp
делает вещи побайтово, поэтому, вероятно, не хватит памяти (только что проверил это на двух файлах по 7 ГБ) - но вы, возможно, ищете более подробную информацию, чем список "файлов X и Y, различающихся по байту х, линия у". Если сходство ваших файлов смещено (например, файл Y имеет идентичный блок текста, но не в том же месте), вы можете передать смещения в cmp
; Вы могли бы превратить его в ресинхронизирующее сравнение с помощью небольшого скрипта.
В сторону: на случай, если кто-нибудь еще приземлится здесь, когда ищет способ подтвердить, что две структуры каталогов (содержащие очень большие файлы) идентичны:diff --recursive --brief
(или же diff -r -q
для краткости или, может быть, даже diff -rq
) будет работать и не хватит памяти.
Я нашел эту ссылку
Может помочь diff -H, или вы можете попробовать установить порт textproc/2bsd-diff, который, очевидно, не пытается загружать файлы в оперативную память, что облегчает работу с большими файлами.
Я не уверен, что вы пробовали эти два варианта или они могли бы работать на вас. Удачи.
Если файлы идентичны (одинаковой длины), за исключением нескольких байтовых значений, вы можете использовать скрипт, подобный следующему (w
это число байтов в строке в hexdump, подстраивается под ширину вашего дисплея):
w=12;
while read -ru7 x && read -ru8 y;
do
[ ".$x" = ".$y" ] || echo "$x | $y";
done 7< <(od -vw$w -tx1z FILE1) 8< <(od -vw$w -tx1z FILE2) > DIFF-FILE1-FILE2 &
less DIFF-FILE1-FILE2
Это не очень быстро, но делает работу.
Если файлы имеют одинаковое количество строк и различаются по содержанию некоторых из них, используйте следующую команду. Замена
\a
(предупреждение) с любым другим символом, который не встречается в файлах.
paste -d $'\a' file1 file2 | awk -F$'\a' '$1 != $2'
Это работает путем объединения строк двух файлов и последующего сравнения каждой пары.
Итак, это не совсем проблема OP, но связанная с ней проблема заключается в том, что у вас есть два больших дампа базы данных, каждый из которых вставляет/записывает свою собственную строку, но различные различия в реализации с плавающей запятой приводят к тому, что числа отклоняются из-за какой-то ошибки IEEE. Благодаря ответу, предоставленному @Diomidis, и обширному однострочному сценарию awk, показанному ниже, мы получаем полностью функционирующий и эффективный нечеткий дифференциал.
Добавьте текст ниже в каталог сценариев какfuzzy-compare.awk
, настройте параметры в разделе BEGIN по мере необходимости (зависящие от локали, режимы отладки и т. д.), а затем перенаправьте выводpaste
внутрь:
paste -d $'\a' file1 file2 | awk -f fuzzy-compare.awk
Пример вывода:
Line 1 diffs found so far: 1 here at field: 4
75747358 1 53 2011-03-29 23:00:00+00 7.428
75747358 1 53 2011-03-28 23:00:00+00 7.428
Line 2 diffs found so far: 2 here at field: 4
75747359 1 53 2011-03-29 23:30:00+00 5.757
75747359 1 53 2011-03-29 23:30:00+01 5.757
Line 3 diffs found so far: 3 here at field: 3
75747360 1 53 2011-03-30 00:00:00+00 6.739
75747360 1 54 2011-03-30 00:00:00+00 6.74
Line 5 diffs found so far: 4
75747362 1 53 2011-03-30 01:00:00+00 6.736 extra-field
75747362 1 53 2011-03-30 01:00:00+00 6.73599999999999977
С отображением разницы:
# diff sample.sql sample2.sql
1,3c1,3
< 75747358 1 53 2011-03-29 23:00:00+00 7.428
< 75747359 1 53 2011-03-29 23:30:00+00 5.757
< 75747360 1 53 2011-03-30 00:00:00+00 6.739
---
> 75747358 1 53 2011-03-28 23:00:00+00 7.428
> 75747359 1 53 2011-03-29 23:30:00+01 5.757
> 75747360 1 54 2011-03-30 00:00:00+00 6.74
5,13c5,13
< 75747362 1 53 2011-03-30 01:00:00+00 6.736 extra-field
< 75747363 1 53 2011-03-30 01:30:00+00 7.576
< 75747364 1 53 2011-03-30 02:00:00+00 6.789
< 75747365 1 53 2011-03-30 02:30:00+00 6.386e+2
< 75747366 1 53 2011-03-30 03:00:00+00 6.016E-2
< 75747367 1 53 2011-03-30 03:30:00+00 6.336
< 75747368 1 53 2011-03-30 04:00:00+00 6.1
< 75747374 1 53 2011-03-30 07:00:00+00 5.9412
< 75747375 1 53 2011-03-30 07:30:00+00 6.137803249
---
> 75747362 1 53 2011-03-30 01:00:00+00 6.73599999999999977
> 75747363 1 53 2011-03-30 01:30:00+00 7.576e+10
> 75747364 1 53 2011-03-30 02:00:00+00 6.789e-10
> 75747365 1 53 2011-03-30 02:30:00+00 6.38600000000000012e+2
> 75747366 1 53 2011-03-30 03:00:00+00 6.01600000000000001E-2
> 75747367 1 53 2011-03-30 03:30:00+00 6.3360000000000003
> 75747368 1 53 2011-03-30 04:00:00+00 6.0999999999999993
> 75747374 1 53 2011-03-30 07:00:00+00 5.94099999999999984
> 75747375 1 53 2011-03-30 07:30:00+00 6.13780324900000007
Код ниже (дублирован в суть github: https://gist.github.com/otheus/92162e3a764d2697c3272b98b2663a94).
#!/bin/awk -f
## Awk script to compare to SQL (postgres) dumps for which each line of input is a row
## and has been preprocessed by
## paste -d $'\a' file1 file2
## The BEL symbol is used by this program to quickly split the input
##
## Sometimes, numbers differ by some kind of rounding error / floating-point implementation
## Ignore that error by subtracting the two values and seeing if they are < maxdiff,
## maxdiff = 1 / (10 ^ (length-after-decimal-point(shortest-value))
## Consider:
## 4.2 vs 4.19998
## The shortest number is 4.2, its length is
## Notes:
## d is the global *d*iff counter
## p is the *p*osition / field that first had a difference
## i is a loop variable,usually current field
## L is the array of fields from the current line of the *L*eft-file
## R is " " " " " " " " " " " *R*ight-file
## clhs is the number of fields in L
## crhs is the number of fields in R
BEGIN {
FS="\a";
DECIMAL_SEP=".";
FIELD_SEP="\t"; # for postgresql; for mysql, maybe ", ";
MAX_DIFFS=10;
DEBUG=0;
# Efficiently fill out our table of maximum tolerances of values
Maxdiffs[1] = 0.1;
for (i=2; i<31; ++i)
Maxdiffs[i] = Maxdiffs[i-1] / 10;
p=-1; # everything starts out fine.
}
# if -v start=...., skip until that line
NR < (0 + start) { next }
# When pairs don't match, investigate further...
("_" $1) != ("_" $2) {
if (DEBUG>1) print "Line",NR ": Input lines differed somehow. Investigating...";
p=0; # p is field# where difference was found; 0 means whole line
# split each half into tab-delimited fields
clhs=split($1,L,FIELD_SEP);
crhs=split($2,R,FIELD_SEP);
if (clhs == crhs) {
if (DEBUG>1) print "Line",NR ": Same number of tokens in each line, delimited by '" FIELD_SEP "'";
## compare field by field
p = -1; # if we don't set p in the loop below, no real differences
# Compare each field, until a difference is found
for (i=1; i<=clhs && p<0; ++i) {
# Hint: force this compare to be string-based
if (("_" L[i]) != ("_" R[i])) {
if (DEBUG>1) print "Line",NR ": Field",i,"differs somehow";
## They differ... but are they numbers?
if ( \
L[i] ~ /^-?[0-9]+\.[0-9]+([eE][-+][0-9]+)?$/ && \
R[i] ~ /^-?[0-9]+\.[0-9]+([eE][-+][0-9]+)?$/ \
) {
# both fields are floating-point numbers, compare loosely
# strip exponent part
sub(/[eE].*/,"",L[i]);sub(/[eE].*/,"",R[i]);
# determine precision of shortest value
precision=( \
length(L[i]) < length(R[i]) ? \
length(L[i]) - index(L[i],DECIMAL_SEP) : \
length(R[i]) - index(R[i],DECIMAL_SEP) \
);
# look up the maxdiff from our table
maxdiff=Maxdiffs[precision];
diff=(L[1] - R[1]);
if (diff > maxdiff || diff < -maxdiff) {
if (DEBUG) print "Line",NR ": Numbers differed at",i,"between",L[i],"and",R[i],"differing more than",maxdiff;
p=i;
}
else {
if (DEBUG) print "Line",NR ": Numbers differed at",i,"between",L[i],"and",R[i],"but differed less than",maxdiff;
}
}
else {
if (DEBUG) print "Line",NR ": Strings or ints differed at",i,"between",L[i],"and",R[i];
p=i;
}
}
else {
if (DEBUG) print "Line",NR ": No differences found";
}
}
}
# else, field count is different, so whole line is.
else {
if (DEBUG) print "Line",NR ": Number of fields in line differ";
}
}
p>=0 {
++d; # bump total diffs count
# Output a little header for each non-matching records
print "Line",NR,"diffs found so far:",d,(p ? "here at field: " p : "" );
# Output the lines that didnt match
print $1; print $2; print "";
p=-1;
}
# Progress counter
NR % 100000 == 0 { print "Line",NR }
d > MAX_DIFFS { exit(1);}
Обратите внимание: до публикации приведенный выше код был однострочным.
Это может не работать для всех типов файлов, но если ваши файлы имеют обычную структуру, вы можете разделить их на более мелкие части и
diff
куски индивидуально.
Например:
csplit large-file.txt '/separator pattern/' '{*}'
Предостережение: это работает, только если в вашем файле есть что-то, что вы можете использовать разделитель, не создавая сотни небольших файлов, и где более мелкие фрагменты все еще сопоставимы.