Testen

Testen

Ich möchte in einem Verzeichnis doppelte Dateien finden und dann alle bis auf eine löschen, um Speicherplatz freizugeben. Wie erreiche ich dies mithilfe eines Shell-Skripts?

Zum Beispiel:

pwd
folder

Die darin enthaltenen Dateien sind:

log.bkp
log
extract.bkp
extract

Ich muss log.bkp mit allen anderen Dateien vergleichen und wenn eine doppelte Datei (anhand ihres Inhalts) gefunden wird, muss ich sie löschen. Ebenso muss die Datei „log“ mit allen anderen Dateien, die folgen, überprüft werden und so weiter.

Bisher habe ich dies geschrieben, aber es führt nicht zum gewünschten Ergebnis.

#!/usr/bin/env ksh
count=`ls -ltrh /folder | grep '^-'|wc -l`
for i in `/folder/*`
do
   for (( j=i+1; j<=count; j++ ))
   do
      echo "Current two files are $i and $j"
      sdiff -s $i  $j
      if [ `echo $?` -eq  0 ]
      then
         echo "Contents of $i and $j are same"
       fi
    done
 done

Antwort1

Wenn Sie einfach ein Befehlszeilentool verwenden möchten und kein Shell-Skript erstellen müssen, fdupessteht das Programm hierfür in den meisten Distributionen zur Verfügung.

Es gibt auch ein GUI-basiertes fslintTool mit derselben Funktionalität.

Antwort2

Diese Lösung findet Duplikate in O(n) Zeit. Für jede Datei wird eine Prüfsumme generiert und jede Datei wird wiederum über ein assoziatives Array mit dem Satz bekannter Prüfsummen verglichen.

#!/bin/bash
#
# Usage:  ./delete-duplicates.sh  [<files...>]
#
declare -A filecksums

# No args, use files in current directory
test 0 -eq $# && set -- *

for file in "$@"
do
    # Files only (also no symlinks)
    [[ -f "$file" ]] && [[ ! -h "$file" ]] || continue

    # Generate the checksum
    cksum=$(cksum <"$file" | tr ' ' _)

    # Have we already got this one?
    if [[ -n "${filecksums[$cksum]}" ]] && [[ "${filecksums[$cksum]}" != "$file" ]]
    then
        echo "Found '$file' is a duplicate of '${filecksums[$cksum]}'" >&2
        echo rm -f "$file"
    else
        filecksums[$cksum]="$file"
    fi
done

Wenn Sie in der Befehlszeile keine Dateien (oder Platzhalter) angeben, wird der Satz von Dateien im aktuellen Verzeichnis verwendet. Es werden Dateien in mehreren Verzeichnissen verglichen, aber es ist nicht so geschrieben, dass es rekursiv in die Verzeichnisse selbst eindringt.

Die „erste“ Datei im Set wird immer als die endgültige Version betrachtet. Dateizeiten, Berechtigungen oder Eigentümer werden nicht berücksichtigt. Nur der Inhalt wird berücksichtigt.

Entfernen Sie das echoaus der rm -f "$file"Zeile, wenn Sie sicher sind, dass es das gewünschte Ergebnis liefert. Beachten Sie, dass Sie ln -f "${filecksums[$cksum]}" "$file"den Inhalt fest verknüpfen könnten, wenn Sie diese Zeile durch ersetzen. Sie sparen dadurch Speicherplatz, verlieren aber nicht die Dateinamen.

Antwort3

Das Hauptproblem in Ihrem Skript scheint zu sein, dass idie tatsächlichen Dateinamen als Werte verwendet werden, während jnur eine Zahl ist. Die Namen in ein Array zu übernehmen und sowohl als auch ials jIndizes zu verwenden, sollte funktionieren:

files=(*)
count=${#files[@]}
for (( i=0 ; i < count ;i++ )); do 
    for (( j=i+1 ; j < count ; j++ )); do
        if diff -q "${files[i]}" "${files[j]}"  >/dev/null ; then
            echo "${files[i]} and ${files[j]} are the same"
        fi
    done
done

ksh(Scheint mit Bash und / zu funktionieren, das ksh93Debian hat.)

Die Zuweisung a=(this that)würde das Array amit den beiden Elementen thisund that(mit den Indizes 0 und 1) initialisieren. Worttrennung und Globbing funktionieren wie üblich, daher wird mit den Namen aller Dateien im aktuellen Verzeichnis (außer Dotfiles) files=(*)initialisiert . würde auf alle Elemente des Arrays erweitert, und das Rautezeichen verlangt eine Länge, also die Anzahl der Elemente im Array. (Beachten Sie, dass dies das erste Element des Arrays wäre und die Länge des ersten Elements ist, nicht des Arrays!)files"${files[@]}"${#files[@]}${files}${#files}

for i in `/folder/*`

Die Backticks hier sind sicher ein Tippfehler? Sie würden die erste Datei als Befehl ausführen und den Rest als Argumente übergeben.

Antwort4

Übrigens ist es eine gute Idee, Prüfsummen oder Hashes zu verwenden. Mein Skript verwendet sie nicht. Aber wenn die Dateien klein und die Anzahl der Dateien nicht groß ist (etwa 10-20 Dateien), wird dieses Skript ziemlich schnell arbeiten. Wenn Sie 100 Dateien oder mehr haben, 1000 Zeilen in jeder Datei, wird die Zeit mehr als 10 Sekunden betragen.

Verwendung: ./duplicate_removing.sh files/*

#!/bin/bash

for target_file in "$@"; do
    shift
    for candidate_file in "$@"; do
        compare=$(diff -q "$target_file" "$candidate_file")
        if [ -z "$compare" ]; then
            echo the "$target_file" is a copy "$candidate_file"
            echo rm -v "$candidate_file"
        fi
    done
done

Testen

Zufällige Dateien erstellen: ./creating_random_files.sh

#!/bin/bash

file_amount=10
files_dir="files"

mkdir -p "$files_dir"

while ((file_amount)); do
    content=$(shuf -i 1-1000)
    echo "$RANDOM" "$content" | tee "${files_dir}/${file_amount}".txt{,.copied} > /dev/null
    ((file_amount--))
done

Laufen ./duplicate_removing.sh files/* und erhalten Sie die Ausgabe

the files/10.txt is a copy files/10.txt.copied
rm -v files/10.txt.copied
the files/1.txt is a copy files/1.txt.copied
rm -v files/1.txt.copied
the files/2.txt is a copy files/2.txt.copied
rm -v files/2.txt.copied
the files/3.txt is a copy files/3.txt.copied
rm -v files/3.txt.copied
the files/4.txt is a copy files/4.txt.copied
rm -v files/4.txt.copied
the files/5.txt is a copy files/5.txt.copied
rm -v files/5.txt.copied
the files/6.txt is a copy files/6.txt.copied
rm -v files/6.txt.copied
the files/7.txt is a copy files/7.txt.copied
rm -v files/7.txt.copied
the files/8.txt is a copy files/8.txt.copied
rm -v files/8.txt.copied
the files/9.txt is a copy files/9.txt.copied
rm -v files/9.txt.copied

verwandte Informationen