Find dubletter og slet dem

For lidt tid siden var jeg i gang med at overføre alle mine filer fra min gamle mac til den nye. Her opdagede jeg at jeg egentlig har en del filer der synes at være dubletter. Specielt billeder i iPhoto/Photos. Jeg har ved samme lejlighed valgt at uploade alle mine billeder over på min flickr konto, og her var flickr uploader i gang at frasortere dubletter. Den gjorde et ok stykke arbejde, men der var stadig en del filer der ikke blev betragtet som dubletter. Men dette program, slettede heller ikke filer på min mac. Den ville bare ikke uploade dubletter. Så jeg var langt fra løsningen, om at komme dubletter til livs.

Jeg søgte så efter diverse programmer til formålet, og efter at afprøve alle gratis programmer, der ikke duede noget, besluttede jeg at købe programmet Gemini. Hvorfor jeg købte dette og ikke et af de mange andre, ved jeg ikke helt. Men, jeg havde har i længere tid haft god erfaring med CleanMyMac (både 2 og 3) og mente at deres program måtte jo kunne også gøre det ligeså godt som CleanMyMac. Det var trods alt, samme udvikler. Den fandt også nogle flere dubletter og dem fik jeg slettet. Men… jeg synes stadig at der var en del tilbage efterfølgende.

Så måtte jeg ligesom i gang om at finde ud af, hvordan jeg selv kan gøre det. Jeg vidste i forvejen at man kunne udregne et værdi (hash) af filen, der tager i betragtning filens størrelse. Og ville se om ud fra denne værdig jeg kan lige finde alle de filer der er dubletter…

Ud fra 17000+ billeder i min Pictures mappe, var der 4300+ der var dubletter. Altså, efter Gemini var færdig med at finde og slette dubletter.

Gemini fortæller mig, der ikke findes dubletter
Gemini fortæller mig, der ikke findes dubletter

Så nu gik jeg i gang med at undersøge om de dubletter, min process har fundet, er egentlig dubletter, så jeg plukkede 200 tilfældige dubletter og tjekkede op på dem. Alle var rigtige nok. Dermed konkluderede jeg at det må være måden, hvordan man kan finde dubletter.

Nu skal jeg finde en måde, hvordan de kan slettes. Først ville jeg lave det sådan at man bliver præsenteret med filerne og derved vælge hvilken en der skal slettes. Men, efter jeg har lavet 30-40% af det, besluttede jeg, at det er måske overkill at gøre sådan. Så jeg nøjedes blot med at slette en af kopi. Det kan sgu være ligegyldig hvilken en jeg slettede. De var kun nøjagtige kopier den fandt, så selv den mindste ændring vil resultere andet hash-værdi.

Så min fremgangsmåde endte med at bruge lidt bash og PHP, for at slette dubletter. Det kan sikkert laves med andre scripting sprog, men det var disse jeg var fortrolig med og derfor brugte jeg dem. Desuden, findes de som standard på OS X, og derfor noget der kan måske genbruges af andre.

Nedenstående er bash script jeg har kaldt “finger.sh” i det at den beregner en fingeraftryk af hver fil den er sat til at undersøge. Her var det billeder. Men det kan ligeså godt have været andre filer.

echo "Generating MD5 hashes..."
## find all file that have given extensions and generate md5 hash. save output to fingerprint.csv file
## replace "Picture" from below line to reflect folder name you want to check for duplicates.
find −E Pictures −type f −regex ".*.(jpg|gif|png|jpeg|psd|CRW)" −exec md5 {} ; > fingerprint.csv
echo "Almost done... Let's make it nice CSV file"
sFIND="MD5 (" ; sREPLACE=""; cat fingerprint.csv | sed −e "s/$sFIND/$sREPLACE/g" > tmp.csv
sFIND=") = " ; sREPLACE="; "; cat tmp.csv | sed −e "s/$sFIND/$sREPLACE/g" > fingerprint.csv
rm −f ./tmp.csv
echo "Generate list of files to delete..."
php read.php
echo "List is done. Now deleting... no turning back now..."
echo "Deleting..."
#xargs −a delete.txt −d'n' rm
echo "Deleting done. Program exiting..."

Og her er så koden til PHP filen. Den er ikke køn og heller ikke noget der er brugervenligt som sådan. Men det virker efter hensigten. Filen hedder read.php, og denne fil henvises i bash scriptet. Så hvis man vælger at kalde filer noget andet, skal man huske at ændre det i bash filen.

function returndup($arr)
{
return array_diff_key($arr, array_unique($arr));
}

// read CSV into an ARRAY
$file = "/Volumes/Xtra/fingerprint.csv"; // This variable needs to be edited to point to samme directory of read.php file
$csv = file($file);
$arr = array();
for ($i=0; $i < sizeof($csv); $i++) {
$tmp = explode("; ",$csv[$i]);
$arr[$i]["filename"] = $tmp[0];
$arr[$i]['md5'] = $tmp[1];
# create two seperate arrays as well for comparation
$cpFile[$i] = $tmp[0];
$cpMd5[$i] = $tmp[1];
}
$tmp = returndup($cpMd5);
foreach ($tmp as $key => $value) {
$dup[$key]["original"] = $arr[array_search($value, array_column($arr, 'md5'))]["filename"];
$dup[$key]["duplicate"] = $cpFile[$key];
$dup[$key]["md5"] = $cpMd5[$key];
}
while (list($key, $value) = each($dup)) {
$block .= str_replace(" ", " ", $dup[$key]["original"]) ."n";
}
$output .= $block;
file_put_contents('delete.txt', $output);

Hvordan bruges det?

Jeg har puttet finger.sh og read.php filer udenfor mappen Pictures. Det er denne mappe jeg gerne ville gennemsøge for dubletter.
Og så skal man ellers fra Terminal’en køre ./finger.sh (husk finger.sh skal være eksekverbart eg. chmod +x finger.sh) … resten klarer den selv.

OBS: Filerne er indstillet at slette automatisk den ene kopi af dubletter, og det har virket fint for mig. Det skal bruges på egen ansvar, jeg tager ingen ansvar for at man har valgt at køre min kode og fået slettet det der ikke skulle slettes.

Addendum: Som en meget lille sikkerhed, har jeg valgt at kommentere (#) den linie der egentlig sletter dubletter… Man skal fjerne ‘#’ fra den næstsidste linie i finger.sh før den vil slette filer.