J'essaie actuellement de prendre un fichier (un fichier image tel que test1.jpg) et j'ai besoin d'une liste de tous les doublons de ce fichier (par contenu). J'ai essayé fdupes
mais cela ne permet pas à un fichier d'entrée de se baser sur ses vérifications.
TLDR: J'ai besoin d'un moyen de lister tous les doublons d'un fichier spécifique par leur contenu.
Il est préférable de rechercher une solution via la ligne de commande, mais des applications complètes conviendront également.
Recherchez d’abord le hachage md5 de votre fichier:
$ md5sum path/to/file
e740926ec3fce151a68abfbdac3787aa path/to/file
(la première ligne est la commande que vous devez exécuter, la deuxième ligne est le hash md5 de ce fichier)
Copiez ensuite le hachage (ce serait différent dans votre cas) et collez-le dans la commande suivante:
$ find . -type f -print0 | xargs -0 md5sum | grep e740926ec3fce151a68abfbdac3787aa
e740926ec3fce151a68abfbdac3787aa ./path/to/file
e740926ec3fce151a68abfbdac3787aa ./path/to/other/file/with/same/content
....
Si vous voulez avoir envie, vous pouvez combiner les 2 en une seule commande:
$ find . -type f -print0 | xargs -0 md5sum | grep `md5sum path/to/file | cut -d " " -f 1`
e740926ec3fce151a68abfbdac3787aa ./path/to/file
e740926ec3fce151a68abfbdac3787aa ./path/to/other/file/with/same/content
....
Vous pouvez utiliser sha1 ou n'importe lequel des autres hachages fantaisie si vous le souhaitez.
Éditer
Si le cas d'utilisation consiste à rechercher dans "plusieurs MP4 ou fichiers ISO de plusieurs gigaoctets" pour trouver un "4 KB jpg" (comme indiqué dans la réponse @Tijn), la spécification de la taille du fichier accélèrerait considérablement.
Si la taille du fichier que vous recherchez est exactement de 3952 octets (comme vous pouvez le constater avec ls -l path/to/file
alors cette commande s'exécute beaucoup plus rapidement:
$ find . -type f -size 3952c -print0 | xargs -0 md5sum | grep e740926ec3fce151a68abfbdac3787aa
e740926ec3fce151a68abfbdac3787aa ./path/to/file
e740926ec3fce151a68abfbdac3787aa ./path/to/other/file/with/same/content
Notez le supplément c
après la taille, indiquant les caractères/octets.
Si vous le souhaitez, vous pouvez combiner cela en une seule commande:
FILE=./path/to/file && find . -type f -size $(du -b $FILE | cut -f1)c -print0 | xargs -0 md5sum | grep $(md5sum $FILE | cut -f1 -d " ")
Utilisez la commande diff avec les opérateurs booléens &&
et ||
bash-4.3$ diff /etc/passwd passwd_duplicate.txt > /dev/null && echo "SAME CONTENT" || echo "CONTENT DIFFERS"
SAME CONTENT
bash-4.3$ diff /etc/passwd TESTFILE.txt > /dev/null && echo "SAME CONTENT" || echo "CONTENT DIFFERS"
CONTENT DIFFERS
Si vous voulez parcourir plusieurs fichiers dans un répertoire spécifique, cd
ici et utilisez une boucle for
comme ceci:
bash-4.3$ for file in * ; do diff /etc/passwd "$file" > /dev/null && echo "$file has same contents" || echo "$file has different contents"; done
also-waste.txt has different contents
directory_cleaner.py has different contents
dontdeletethisfile.txt has different contents
dont-delete.txt has different contents
important.txt has different contents
list.txt has different contents
neverdeletethis.txt has different contents
never-used-it.txt has different contents
passwd_dulicate.txt has same contents
Pour les cas récursifs, utilisez la commande find
pour parcourir le répertoire et tous ses sous-répertoires (sans oublier les guillemets et toutes les barres obliques appropriées):
bash-4.3$ find . -type f -exec sh -c 'diff /etc/passwd "{}" > /dev/null && echo "{} same" || echo "{} differs"' \;
./reallyimportantfile.txt differs
./dont-delete.txt differs
./directory_cleaner.py differs
./TESTFILE.txt differs
./dontdeletethisfile.txt differs
./neverdeletethis.txt differs
./important.txt differs
./passwd_dulicate.txt same
./this-can-be-deleted.txt differs
./also-waste.txt differs
./never-used-it.txt differs
./list.txt differs
Obtenez le md5sum
du fichier en question et enregistrez-le dans une variable, par exemple. md5
:
md5=$(md5sum file.txt | awk '{print $1}')
Utilisez find
pour parcourir l’arborescence de répertoires souhaitée et vérifiez si un fichier a la même valeur de hachage. Le cas échéant, indiquez le nom du fichier:
find . -type f -exec sh -c '[ "$(md5sum "$1" | awk "{print \$1}")" = "$2" ] \
&& echo "$1"' _ {} "$md5" \;
find . -type f
trouve tous les fichiers dans le répertoire actuel, changez le répertoire pour répondre à vos besoins
le prédicat -exec
exécute la commande sh -c ...
sur tous les fichiers trouvés
Dans sh -c
, _
est un espace réservé pour $0
, $1
est le fichier trouvé, $2
est $md5
.
[ $(md5sum "$1"|awk "{print \$1}") = "$2" ] && echo "$1"
affiche le nom du fichier si la valeur de hachage du fichier est identique à celle pour laquelle nous vérifions les doublons.
Exemple:
% md5sum ../foo.txt bar.txt
d41d8cd98f00b204e9800998ecf8427e ../foo.txt
d41d8cd98f00b204e9800998ecf8427e bar.txt
% md5=$(md5sum ../foo.txt | awk '{print $1}')
% find . -type f -exec sh -c '[ "$(md5sum "$1" | awk "{print \$1}")" = "$2" ] && echo "$1"' _ {} "$md5" \;
bar.txt
@smurf et @heemayl ont certes raison, mais j’ai découvert que dans mon cas, c’était plus lent que je ne le voulais; J'ai tout simplement trop de fichiers à traiter. Par conséquent, j’ai écrit un petit outil en ligne de commande qui, je pense, pourrait vous aider aussi. ( https://github.com/tijn/dupfinder ; Ruby; pas de dépendances externes)
Fondamentalement, mon script reporte le calcul du hachage: il effectuera le calcul uniquement lorsque la taille des fichiers correspond. Depuis pourquoi voudrais-je diffuser en streaming le contenu de plusieurs MP4 ou fichiers iso de plusieurs gigaoctets à l'aide d'un algorithme de hachage, alors que je sais que je recherche un fichier jpg de 4 Ko!? Le reste du script consiste principalement en un format de sortie.
Edit: (merci @Serg) Voici le code source de tout le script. Vous devriez le sauvegarder dans ~/bin/find-dups
ou peut-être même /usr/local/bin/find-dups
, puis utilisez chmod +x
pour le rendre exécutable. Ruby doit être installé, mais sinon, il n'y a pas d'autres dépendances.
#!/usr/bin/env Ruby
require 'digest/md5'
require 'fileutils'
require 'optparse'
def glob_from_argument(arg)
if File.directory?(arg)
arg + '/**/*'
elsif File.file?(arg)
arg
else # it's already a glob
arg
end
end
# Wrap text at 80 chars. (configurable)
def wrap_text(*args)
width = args.last.is_a?(Integer) ? args.pop : 80
words = args.flatten.join(' ').split(' ')
if words.any? { |Word| Word.size > width }
raise NotImplementedError, 'cannot deal with long words'
end
lines = []
line = []
until words.empty?
Word = words.first
if line.size + line.map(&:size).inject(0, :+) + Word.size > width
lines << line.join(' ')
line = []
else
line << words.shift
end
end
lines << line.join(' ') unless line.empty?
lines.join("\n")
end
ALLOWED_PRINT_OPTIONS = %w(hay needle separator)
def parse_options(args)
options = {}
options[:print] = %w(hay needle)
opt_parser = OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options] HAYSTACK NEEDLES"
opts.separator ''
opts.separator 'Search for duplicate files (needles) in a directory (the haystack).'
opts.separator ''
opts.separator 'HAYSTACK should be the directory (or one file) that you want to search in.'
opts.separator ''
opts.separator wrap_text(
'NEEDLES are the files you want to search for.',
'A NEEDLE can be a file or a directory,',
'in which case it will be recursively scanned.',
'Note that NEEDLES may overlap the HAYSTACK.')
opts.separator ''
opts.on("-p", "--print PROPERTIES", Array,
"When a match is found, print needle, or",
"hay, or both. PROPERTIES is a comma-",
"separated list with one or more of the",
"words 'needle', 'hay', or 'separator'.",
"'separator' prints an empty line.",
"Default: 'needle,hay'") do |list|
options[:print] = list
end
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end
opt_parser.parse!(args)
options[:haystack] = ARGV.shift
options[:needles] = ARGV.shift(ARGV.size)
raise ArgumentError, "Missing HAYSTACK" if options[:haystack].nil?
raise ArgumentError, "Missing NEEDLES" if options[:needles].empty?
unless options[:print].all? { |option| ALLOWED_PRINT_OPTIONS.include? option }
raise ArgumentError, "Allowed print options are 'needle', 'hay', 'separator'"
end
options
rescue OptionParser::InvalidOption, ArgumentError => error
puts error, nil, opt_parser.banner
exit 1
end
options = parse_options(ARGV)
VERBOSE = options[:verbose]
PRINT_HAY = options[:print].include? 'hay'
PRINT_NEEDLE = options[:print].include? 'needle'
PRINT_SEPARATOR = options[:print].include? 'separator'
HAYSTACK_GLOB = glob_from_argument options[:haystack]
NEEDLES_GLOB = options[:needles].map { |arg| glob_from_argument(arg) }
def info(*strings)
return unless VERBOSE
STDERR.puts strings
end
def info_with_ellips(string)
return unless VERBOSE
STDERR.print string + '... '
end
def all_files(*globs)
globs
.map { |glob| Dir.glob(glob) }
.flatten
.map { |filename| File.expand_path(filename) } # normalize filenames
.uniq
.sort
.select { |filename| File.file?(filename) }
end
def index_haystack(glob)
all_files(glob).group_by { |filename| File.size(filename) }
end
@checksums = {}
def checksum(filename)
@checksums[filename] ||= calculate_checksum(filename)
end
def calculate_checksum(filename)
Digest::MD5.hexdigest(File.read(filename))
end
def find_needle(needle, haystack)
straws = haystack[File.size(needle)] || return
checksum_needle = calculate_checksum(needle)
straws.detect do |straw|
straw != needle &&
checksum(straw) == checksum_needle &&
FileUtils.identical?(needle, straw)
end
end
BOLD = "\033[1m"
NORMAL = "\033[22m"
def print_found(needle, found)
if PRINT_NEEDLE
print BOLD if $stdout.tty?
puts needle
print NORMAL if $stdout.tty?
end
puts found if PRINT_HAY
puts if PRINT_SEPARATOR
end
info "Searching #{HAYSTACK_GLOB} for files equal to #{NEEDLES_GLOB}."
info_with_ellips 'Indexing haystack by file size'
haystack = index_haystack(HAYSTACK_GLOB)
haystack[0] = nil # ignore empty files
info "#{haystack.size} files"
info 'Comparing...'
all_files(*NEEDLES_GLOB).each do |needle|
info " examining #{needle}"
found = find_needle(needle, haystack)
print_found(needle, found) unless found.nil?
end
Il est possible d'utiliser l'option -c
de md5sum
sur la ligne de commande, si vous manipulez un peu son flux d'entrée. La commande suivante n'est pas récursive, elle ne fonctionnera que dans le répertoire de travail actuel. Remplacez original_file
par le nom de fichier avec lequel vous souhaitez vérifier les doublons.
(hash=$(md5sum original_file) ; for f in ./* ; do echo "${hash%% *} ${f}" | if md5sum -c --status 2>/dev/null ; then echo "$f is a duplicate" ; fi ; done)
Vous pouvez remplacer la partie for f in ./*
par for f in /directory/path/*
pour effectuer une recherche dans un autre répertoire.
Si vous souhaitez que la recherche passe en revue dans les annuaires, vous pouvez définir l'option Shell "globstar" et utiliser deux étoiles dans le modèle donné à la boucle for:
(shopt -s globstar; hash=$(md5sum original_file); for f in ./** ; do echo "${hash%% *} ${f}" | if md5sum -c --status 2>/dev/null; then echo "$f is a duplicate"; fi; done)
Les deux versions de la commande ne généreront que le nom des fichiers en double avec l'instruction ./file is a duplicate
. Ils sont tous deux encapsulés entre parenthèses afin d'éviter de définir la variable hash ou l'option globstar Shell en dehors de la commande elle-même. La commande peut utiliser d'autres algorithmes de hachage tels que sha256sum
, il vous suffit de remplacer les deux occurrences de md5sum
pour y parvenir.