Найдите дубликаты файла содержанием

Я в настоящее время пытаюсь взять файл (файл изображения, такой как test1.jpg), и у меня должен быть список всех дубликатов того файла (содержанием). Я попробовал fdupes но это не позволяет входному файлу основывать свои проверки вокруг.

TLDR: Мне нужен способ перечислить все дубликаты определенного файла их содержанием.

Предпочтительный поиск решения через командную строку, но полных приложений будет прекрасен также.

7
задан 10 October 2016 в 15:01

6 ответов

Сначала найдите md5 хеш своего файла:

$ md5sum path/to/file
e740926ec3fce151a68abfbdac3787aa  path/to/file

(первая строка является командой, которую необходимо выполнить, вторая строка является md5 хешем того файла)

, Затем копируют хеш (это отличалось бы в случае), и вставьте его в следующую команду:

$ find . -type f -print0 | xargs -0 md5sum | grep e740926ec3fce151a68abfbdac3787aa
e740926ec3fce151a68abfbdac3787aa  ./path/to/file
e740926ec3fce151a68abfbdac3787aa  ./path/to/other/file/with/same/content
....

, Если Вы хотите стать необычными, Вы могли бы объединить 2 в единственной команде:

$ 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
....

Вы могли использовать sha1 или любой из других необычных хешей, если Вы хотите.

Редактирование

, Если вариант использования должен перерыть "MP4s на несколько мультигигабайтов или изофайлы" для нахождения "4 КБ jpg" (согласно ответу @Tijn) затем определение размера файла ускорило бы вещи существенно.

, Если размер файла Вы ищете, точно 3 952 байта (Вы видите, что с помощью ls -l path/to/file затем эта команда работала бы намного быстрее:

$ find . -type f -size 3952c -print0 | xargs -0 md5sum | grep e740926ec3fce151a68abfbdac3787aa
e740926ec3fce151a68abfbdac3787aa  ./path/to/file
e740926ec3fce151a68abfbdac3787aa  ./path/to/other/file/with/same/content

Примечание дополнительное c после размера, указывая на символы/байты.

, Если Вы хотите Вас, мог бы объединить это в единственной команде:

FILE=./path/to/file && find . -type f -size $(du -b $FILE | cut -f1)c -print0 | xargs -0 md5sum | grep $(md5sum $FILE | cut -f1 -d " ")
9
ответ дан 23 November 2019 в 06:11

Можно использовать filecmp в Python

, Например:

import filecmp 
print filecmp.cmp('filename.png', 'filename.png') 

распечатает Верный , если будет равняться, иначе Ложь

4
ответ дан 23 November 2019 в 06:11

Возможно использовать -c опция md5sum на командной строке, если Вы делаете немного управления ее входным потоком. Следующая команда не является рекурсивной, она будет только работать в существующем рабочем каталоге. Замена original_file с именем файла Вы хотите проверить дубликаты по.

(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)

можно заменить эти for f in ./*, расстаются с for f in /directory/path/* для поиска другого каталога.

, Если Вы хотели бы, чтобы поиск рекурсивно вызвал через каталоги, можно установить опцию оболочки 'globstar' и использовать две звезды в шаблоне, данном для цикла:

(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)

Обе версии команды только произведут название дубликатов файлов с оператором ./file is a duplicate. Они оба инкапсулируются в скобках, чтобы не устанавливать переменную хеша или опцию оболочки globstar за пределами самой команды. Команда может использовать другие алгоритмы хеширования такой в качестве sha256sum, просто заменить два случаев md5sum для достижения этого.

1
ответ дан 23 November 2019 в 06:11

Используйте разность команда с булевыми операторами && и ||

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

, Если Вы хотите пробежаться через несколько файлов в определенном каталоге, cd туда и использовать for цикл как так:

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

Для рекурсивных случаев, используйте find команда для пересечения каталога, и все его подкаталоги (возражайте против кавычек и всех соответствующих наклонных черт):

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
4
ответ дан 23 November 2019 в 06:11

Доберитесь md5sum из рассматриваемого файла, и сохраняют в переменной, например. md5:

md5=$(md5sum file.txt | awk '{print $1}')

Использовать find для пересечения желаемого дерева каталогов и проверки, если какой-либо файл имеет то же значение хэш-функции, раз так печатают имя файла:

find . -type f -exec sh -c '[ "$(md5sum "$1" | awk "{print \$1}")" = "$2" ] \
                             && echo "$1"' _ {} "$md5" \;
  • find . -type f находит все файлы в текущем каталоге, измените каталог для удовлетворения потребностей

  • -exec предикат выполняет команду sh -c ... на всех найденных файлах

  • В sh -c, _ заполнитель для $0, $1 найденный файл, $2 $md5

  • [ $(md5sum "$1"|awk "{print \$1}") = "$2" ] && echo "$1" печатает имя файла, если значение хэш-функции файла - то же как то, мы проверяем дубликаты на

Пример:

% 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
2
ответ дан 23 November 2019 в 06:11

@smurf и @heemayl, конечно, корректны, но я узнал, что в моем случае это было медленнее, чем я хотел, чтобы он был; у меня просто было слишком много файлов для обработки. Поэтому я записал маленький инструмент командной строки, что я думаю, мог бы помочь Вам также. (https://github.com/tijn/dupfinder; рубин; никакие внешние зависимости)

В основном мой сценарий откладывает вычисление хеша: это только выполнит вычисление, когда размеры файла будут соответствовать. С тех пор, почему был бы, я хочу передать содержание потоком MP4s на несколько мультигигабайтов или изофайлов через хеш-алгоритм, когда я знаю, что ищу 4 КБ jpg!? Остальная часть сценария главным образом производится, форматируя.

Править: (спасибо @Serg), Вот исходный код целого сценария. Необходимо сохранить его в ~/bin/find-dups или возможно ровный /usr/local/bin/find-dups и затем используйте chmod +x на нем для создания его исполняемым файлом. Этому нужно было установить Ruby, но иначе нет никаких других зависимостей.

#!/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
1
ответ дан 23 November 2019 в 06:11

Другие вопросы по тегам:

Похожие вопросы: