Comparing many files in pairs grows cumbersome very quickly. Ten files requires 45 comparisons. 100 files takes almost 5000. My test set is 595 files (totalling 10 GB), which would require over 175,000 pair comparisons. (This set is 9 dated archive directories, containing metadata of full and partial backups from various partitions.)
The method is to calculate a checksum for every file (taking a total of just over two minutes on a Laptop), and then grouping the files by checksum using awk (taking under a second).
The checksum process is this Bash fragment:
#.. Checksum all the files, and show some statistics.
[ x ] && {
time ( cd "${BackUpDir}" && cksum */* ) > "${CkSums}"
du -s -h "${BackUpDir}"
head -n 3 "${CkSums}"
awk '{ Bytes += $2; }
END { printf ("Files %d, %.2f MB\n", FNR, Bytes / (1024 * 1024)); }
' "${CkSums}"
}
which shows this log.
$ ./fileGroup
real 2m5.139s
user 1m3.141s
sys 0m24.685s
9.8G /media/paul/WinData/tarMETADATA
2288228966 156844 20220107_002000/02_History.tld
1812380507 156992 20220107_002000/02_History.toc
3028427874 1000411 20220107_002000/06_TechHist.tld
Files 565, 10001.10 MB
real 0m0.024s #.. (Runtime of the awk component)
user 0m0.018s
sys 0m0.008s
An extract of the results:
Group of 5 files for cksum 1459775330
20220319_114500/lib64.tld
20220401_182500/lib64.tld
20220407_192000/lib64.tld
20220503_190500/lib64.tld
20220503_232500/lib64.tld
Group of 3 files for cksum 2937156162
20220407_192000/sbin.tld
20220503_190500/sbin.tld
20220503_232500/sbin.tld
Group of 2 files for cksum 3291901599
20220503_190500/30_Photos.tld
20220503_232500/30_Photos.tld
Counted 304 non-grouped files.
The Bash script is around 60 lines, of which 30 are the embedded awk script (I am unaware of any GNU/specific syntax required).
#! /bin/bash --
#.. Determine groups of identical files.
BackUpDir="/media/paul/WinData/tarMETADATA"
CkSums="Cksum.txt"
Groups="Groups.txt"
#.. Group all the files by checksum, and report them.
fileGroup () {
local Awk='
BEGIN { Db = 0; reCut2 = "^[ ]*[^ ]+[ ]+[^ ]+[ ]+"; }
{ if (Db) printf ("\n%s\n", $0); }
#.. Add a new cksum value.
! (($1,0) in Fname) {
Cksum[++Cksum[0]] = $1;
if (Db) printf ("Added Cksum %d value %s.\n",
Cksum[0], Cksum[Cksum[0]]);
Fname[$1,0] = 0;
}
#.. Add a filename.
{
Fname[$1,++Fname[$1,0]] = $0;
sub (reCut2, "", Fname[$1,Fname[$1,0]]);
if (Db) printf ("Fname [%s,%s] is \047%s\047\n",
$1, Fname[$1,0], Fname[$1, Fname[$1,0]]);
}
#.. Report the identical files, grouped by checksum.
function Report (Local, k, ke, cs, j, je, Single) {
ke = Cksum[0];
for (k = 1; k <= ke; ++k) {
cs = Cksum[k];
je = Fname[cs,0];
if (je < 2) { ++Single; continue; }
printf ("\nGroup of %d files for cksum %s\n", je, cs);
for (j = 1; j <= je; ++j) printf (" %s\n", Fname[cs,j]);
}
printf ("\nCounted %d non-grouped files.\n", Single);
}
END { Report( ); }
'
awk -f <( printf '%s' "${Awk}" )
}
#### Script Body Starts Here.
#.. Checksum all the files, and show some statistics.
[ x ] && {
time ( cd "${BackUpDir}" && cksum */* ) > "${CkSums}"
du -s -h "${BackUpDir}"
head -n 3 "${CkSums}"
awk '{ Bytes += $2; }
END { printf ("Files %d, %.2f MB\n", FNR, Bytes / (1024 * 1024)); }
' "${CkSums}"
}
#.. Analyse the cksum data.
time fileGroup < "${CkSums}" > "${Groups}"
fdupes
tool?cksum
once across all files, sort that output so that identical files list consecutively, and use awk to group sets of identical files and report. No n x n scale problem. My use case was to find discrepancies in a set of 12,000 files that should have been identical across 160 workstations, and I let the files "vote" on the most standard set, and generate a script to push out the smallest set of mismatches. I had ssh get each system to do its own cksums and return a 12000-line data file, and compared the whole 2 million files in about 10 minutes.