Binarne / Hash porównujące pliki w różnych lokalizacjach, ignorując strukturę folderów w OSX

2

Potrzebuję sposobu na porównanie plików w dwóch różnych zestawach lokalizacji, najlepiej w sposób nieco dokładniejszy niż sama nazwa pliku.

Niedawno dostaliśmy nowy NAS do biura i przenosiliśmy na niego dane z różnych dysków twardych USB. Chciałbym móc potwierdzić, że wszystkie pliki zostały pomyślnie przeniesione.

Obejrzałem wiele programów do porównywania plików, ale większość z nich jest wrażliwa na strukturę katalogów, w której znajdują się pliki. Idealnie byłoby po prostu chcieć haszować (MD5 lub podobny) wszystkie pliki na dyskach USB1, USB2 i USB3, a następnie haszować wszystkie pliki na NAS-VOLUME1 i NAS-VOLUME2 i porównać listy, aby zobaczyć, których plików brakuje bok.

Podejrzewam, że można to zrobić za pomocą skryptu lub wiersza poleceń, ale nie jestem zbyt obeznany z pracą z wierszem poleceń w OSX (zwykle faceci w systemie Windows).

Wszelkie wskazówki bardzo mile widziane

mynameisdev
źródło
Z Pythonem jest to łatwe. Nie jestem pewien, jakie opcje są dostępne w OSX.
Eric Roper
Właściwie udało mi się wykasować coś w c #, ale wciąż brakuje mi pamięci RAM. Jakieś przykłady skryptów Python, które to zrobią?
mynameisdev

Odpowiedzi:

1

fdupesmógłby to zrobić, jeśli jest dostępny na komputerze Mac. Zamontowałbyś wszystkie dyski i pozwoliłbyś, aby fdupes działało po wszystkich katalogach. Byłoby to również najbardziej wydajne (tylko porównywanie plików o tym samym rozmiarze, ponieważ pliki o unikalnym rozmiarze plików nie mogą być duplikatami itp.). Uważaj, ponieważ fdupes jest często używany do usuwania niechcianych duplikatów, więc wiele przykładów może zawierać opcje usuwania.

frostschutz
źródło
Wydaje się, że nie można znaleźć rozsądnej trasy instalacji dla fdupes na tym komputerze Mac. Mam gdzieś Ubuntu na laptopie, ale w międzyczasie wszelkie inne pomysły, coś z GUI byłoby świetne ...
mynameisdev
0

Ta wersja działa, jeśli nazwy plików i struktury podkatalogów są takie same w obu lokalizacjach. Zaletą tej wersji jest to, że powinna być przyjazna dla pamięci. Zostało to lekko przetestowane pod kątem prostych błędów, ale jest prawdopodobne, że należy uwzględnić więcej. Ponadto podejście bash byłoby znacznie bardziej wydajne, ponieważ python ma dużo narzutu. W zależności od wielkości danych może to zająć dużo czasu.

import os
import hashlib

def hash(file):
  f = open(file,'rb')
  h = hashlib.md5()
  checkEOF = b' '
  while checkEOF != b'':
    checkEOF = f.read(1024)
    h.update(checkEOF)
  f.close()
  return h.hexdigest()


def Hashwalk(d1, d2):
  errlist = []
  log = []
  walkobject1 = os.walk(d1)
  walkobject2 = os.walk(d2)
  try:
    for walks in zip(walkobject1,walkobject2):
      dir1 = walks[0][0]
      dir2 = walks[1][0]
      files1 = walks[0][2]
      files2 = walks[1][2]
      for files in zip(files1,files2):
        try:
          pathfile1 = os.path.join(dir1,files[0])
          pathfile2 = os.path.join(dir2,files[1])
          digest1 = hash(pathfile1)
          digest2 = hash(pathfile2)
          if digest1 != digest2:
            log.append((pathfile1, digest1, pathfile2, digest2))
        except PermissionError as error:    
          errlist.append((pathfile1,error))
        except FileNotFoundError as error:
          errlist.append((pathfile1,error))        
  except KeyboardInterrupt:
    print('Program terminated, results may be incomplete')
  return (log,errlist)

def ToDisk(hw):
  diff = open('differenthashes.txt','w',encoding='utf-8')
  for pair in hw[0]:
    diff.write(pair[0]+','+pair[1]+'\n')
    diff.write(pair[2]+','+pair[3]+'\n')
  if hw[1]:  
    diff.write('\nerrors\n')
    for error in hw[1]:
      diff.write(error[0]+','+error[1]+'\n')
  else:
    diff.write('no errors detected')
  diff.close()

ToDisk(Hashwalk('test1','test2'))
Eric Roper
źródło
0

Obecnie jedynymi błędami, które ten skrypt uwzględnia, są PermissionError i FileNotFoundError. Niektórych znaków nie można poprawnie obsłużyć, ponieważ są one reprezentowane za pomocą łańcucha kodującego, co spowodowało błąd FileNotFoundError. Dodałem wyjątek KeyboardInterrupt na wypadek, gdyby skrypt działał długo i chcesz zobaczyć skumulowane wyniki. Katalog, z którego uruchamiany jest ten skrypt, będzie zawierał plik o nazwie differenthashes.txt.

Aby wykonać, po prostu zamień „path1” i „path2” w wywołaniu funkcji porównawczej () na dole. Daj mi znać, jeśli masz jakieś sugestie lub uważasz, że to nie odpowiada twoim potrzebom.

import os
import hashlib
import time

def hash(file):
  f = open(file,'rb')
  h = hashlib.md5()
  checkEOF = b' '
  while checkEOF != b'':
    checkEOF = f.read(1024)
    h.update(checkEOF)
  f.close()
  return h.hexdigest()

def hashwalk(d = './'):
  errlist = []
  hashes = []
  cwd = os.getcwd()
  os.chdir(d)
  walkobject = os.walk('./')
  try:
    for directory in walkobject:
      dir = directory[0]
      files = directory[2]
      for file in files:
        try:
          pathfile = os.path.join(dir,file)
          digest = hash(pathfile)
          hashes.append((pathfile,digest))
        except PermissionError as error:    
          errlist.append((pathfile,error))
        except FileNotFoundError as error:
          errlist.append((pathfile,error))
  except KeyboardInterrupt:
    print('Program terminated, results may be incomplete')
  os.chdir(cwd)
  return [hashes,errlist]

def compare(path1,path2,logerrors = False):
  loc1 = hashwalk(path1)
  loc2 = hashwalk(path2)
  differenthash = set(loc1[0]).symmetric_difference(set(loc2[0]))
  log = open('differenthashes.txt','w',encoding='utf-8')
  log.write('path                                          hash                                 date modified\n')
  for f,h in sorted(differenthash):
    if (f,h) in loc1[0]:
      print(path1+'\\'+f[2:],h,time.ctime(os.stat(path1+'\\'+f[2:]).st_mtime))
      log.write(path1 + ' ' +f[2:] + ' ' + h + ' ' + time.ctime(os.stat(path1+'\\'+f[2:]).st_mtime)+'\n')
    else:
      print(path2+'\\'+f[2:],h,time.ctime(os.stat(path2+'\\'+f[2:]).st_mtime))
      log.write(path2 + ' ' +f[2:] + ' ' + h + ' ' + time.ctime(os.stat(path2+'\\'+f[2:]).st_mtime)+'\n')
  if logerrors:
    log.write('\n\n'+path1+' errors\n')
    for error in loc1[1]:
      log.write(str(error) + '\n')
    log.write('\n'+path2+' errors\n')
    for error in loc2[1]:
      log.write(str(error) +'\n')
  log.close()

compare('path1', 'path2' ,logerrors=True)
Eric Roper
źródło
Jeśli daje to błąd MemoryError, a struktura podkatalogu jest taka sama w obu lokalizacjach, możesz przenieść porównania bezpośrednio do hashwalk () i porównać bez list. Najpierw wypróbuję tę wersję i edytuję mój post, jeśli działa.
Eric Roper