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, fdupes
steht das Programm hierfür in den meisten Distributionen zur Verfügung.
Es gibt auch ein GUI-basiertes fslint
Tool 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 echo
aus 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 i
die tatsächlichen Dateinamen als Werte verwendet werden, während j
nur eine Zahl ist. Die Namen in ein Array zu übernehmen und sowohl als auch i
als j
Indizes 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 ksh93
Debian hat.)
Die Zuweisung a=(this that)
würde das Array a
mit den beiden Elementen this
und 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