Hoe vervang ik dubbele bestanden met harde koppelingen met python?

Ik ben een fotograaf en doe veel back-ups. Door de jaren heen vond ik mezelf met veel harde schijven. Nu kocht ik een NAS en kopieerde ik al mijn foto's op een 3TB-raid 1 met rsync. Volgens mijn script zijn ongeveer 1 TB van die bestanden duplicaten. Dat komt door het doen van meerdere back-ups voordat ik bestanden op mijn laptop verwijder en erg rommelig ben. Ik heb een back-up van al die bestanden op de oude harde schijven, maar het zou vervelend zijn als mijn script de boel verprutst. Kun je alsjeblieft een kijkje nemen in mijn duplicaatzoekerscript en me vertellen of je denkt dat ik het kan uitvoeren of niet? Ik probeerde het in een testmap en het lijkt goed, maar ik wil de dingen niet verpesten op de NAS.

Het script heeft drie stappen in drie bestanden. In dit eerste deel vind ik alle beeld- en metadata-bestanden en plaats deze in een shelve-database (datenbank) met hun grootte als sleutel.

import os
import shelve

datenbank = shelve.open(os.path.join(os.path.dirname(__file__),"shelve_step1"), flag='c', protocol=None, writeback=False)

#path_to_search = os.path.join(os.path.dirname(__file__),"test")
path_to_search = "/volume1/backup_2tb_wd/"
file_exts = ["xmp", "jpg", "JPG", "XMP", "cr2", "CR2", "PNG", "png", "tiff", "TIFF"]
walker = os.walk(path_to_search)

counter = 0

for dirpath, dirnames, filenames in walker:
  if filenames:
    for filename in filenames:
      counter += 1
      print str(counter)
      for file_ext in file_exts:
        if file_ext in filename:
          filepath = os.path.join(dirpath, filename)
          filesize = str(os.path.getsize(filepath))
          if not filesize in datenbank:
            datenbank[filesize] = []
          tmp = datenbank[filesize]
          if filepath not in tmp:
            tmp.append(filepath)
            datenbank[filesize] = tmp

datenbank.sync()
print "done"
datenbank.close()

Het tweede gedeelte. Nu laat ik alle bestandsgroottes die slechts één bestand in hun lijst hebben, en creëer een andere shelve-database met de md5-hash als sleutel en een lijst met bestanden als waarde.

import os
import shelve
import hashlib

datenbank = shelve.open(os.path.join(os.path.dirname(__file__),"shelve_step1"), flag='c', protocol=None, writeback=False)

datenbank_step2 = shelve.open(os.path.join(os.path.dirname(__file__),"shelve_step2"), flag='c', protocol=None, writeback=False)

counter = 0
space = 0

def md5Checksum(filePath):
    with open(filePath, 'rb') as fh:
        m = hashlib.md5()
        while True:
            data = fh.read(8192)
            if not data:
                break
            m.update(data)
        return m.hexdigest()


for filesize in datenbank:
  filepaths = datenbank[filesize]
  filepath_count = len(filepaths)
  if filepath_count > 1:
    counter += filepath_count -1
    space += (filepath_count -1) * int(filesize)
    for filepath in filepaths:
      print counter
      checksum = md5Checksum(filepath)
      if checksum not in datenbank_step2:
        datenbank_step2[checksum] = []
      temp = datenbank_step2[checksum]
      if filepath not in temp:
        temp.append(filepath)
        datenbank_step2[checksum] = temp

print counter
print str(space)

datenbank_step2.sync()
datenbank_step2.close()
print "done"

En tot slot het gevaarlijkste gedeelte. Voor evrey md5 key haal ik de bestandslijst op en voer een extra sha1 uit. Als het overeenkomt, verwijder ik elk bestand in die lijst, neem de eerste over en maak een harde koppeling om de verwijderde bestanden te vervangen.

import os
import shelve
import hashlib

datenbank = shelve.open(os.path.join(os.path.dirname(__file__),"shelve_step2"), flag='c', protocol=None, writeback=False)

def sha1Checksum(filePath):
    with open(filePath, 'rb') as fh:
        m = hashlib.sha1()
        while True:
            data = fh.read(8192)
            if not data:
                break
            m.update(data)
        return m.hexdigest()

for hashvalue in datenbank:
  switch = True
  for path in datenbank[hashvalue]:
    if switch:
      original = path
      original_checksum = sha1Checksum(path)
      switch = False
    else:
      if sha1Checksum(path) == original_checksum:
        os.unlink(path)
        os.link(original, path)
        print "delete: ", path
print "done"

Wat denk je? Dank u zeer.

* als dat op een of andere manier belangrijk is: het is een synology 713+ en heeft een ext3- of ext4-bestandssysteem.

3
Bewegingen zijn ook erg snel vergeleken met kopieën, dus ... ben je zeker heb je geen tijd? (Overigens denk ik dat ik eigenlijk een hele parallelle structuur zou maken om ze naar te verplaatsen, in plaats van ze allemaal naar een platte map te verplaatsen.Eerst zou een map met 128K-bestanden erin problemen kunnen veroorzaken (voor het bestandssysteem, voor jouw shell, voor je Python-script, etc.) Ten tweede, zelfs als je alle metadata in de database verliest, zal een parallelle boom het triviaal maken om ongedaan te maken.)
toegevoegd de auteur abarnert, de bron
Bewegingen zijn ook erg snel vergeleken met kopieën, dus ... ben je zeker heb je geen tijd? (Overigens denk ik dat ik eigenlijk een hele parallelle structuur zou maken om ze naar te verplaatsen, in plaats van ze allemaal naar een platte map te verplaatsen.Eerst zou een map met 128K-bestanden erin problemen kunnen veroorzaken (voor het bestandssysteem, voor jouw shell, voor je Python-script, etc.) Ten tweede, zelfs als je alle metadata in de database verliest, zal een parallelle boom het triviaal maken om ongedaan te maken.)
toegevoegd de auteur abarnert, de bron
Ondertussen denk ik dat deze vraag thuishoort op Code Review , niet Stack Overflow.
toegevoegd de auteur abarnert, de bron
Ondertussen denk ik dat deze vraag thuishoort op Code Review , niet Stack Overflow.
toegevoegd de auteur abarnert, de bron
@JasonTS: bestanden verplaatsen naar een andere map op hetzelfde bestandssysteem verspilt geen ruimte, en het maken van 128K-hardlinks verspilt een megabyte of zo (waarschijnlijk minder dan je shelve -database), dus dat is waarschijnlijk niet Het is geen goede reden om de suggestie van suspectus af te wijzen.
toegevoegd de auteur abarnert, de bron
plaats in plaats van verwijderen de duplicaten onmiddellijk naar een andere map en verwijder ze allemaal wanneer u tevreden bent dat er niets verloren is gegaan.
toegevoegd de auteur suspectus, de bron
plaats in plaats van verwijderen de duplicaten onmiddellijk naar een andere map en verwijder ze allemaal wanneer u tevreden bent dat er niets verloren is gegaan.
toegevoegd de auteur suspectus, de bron
@abarnert: Ah, sorry dat ik aan een kopie dacht. Nou dat zou leuk kunnen zijn. Maar ik heb de ruimte snel nodig, dus ik denk niet echt dat ik genoeg tijd heb om te zien of er iets mis is of niet. Bedankt voor de tip. Ik heb het ook gepost in Code Review.
toegevoegd de auteur JasonTS, de bron
@abarnert: Ah, sorry dat ik aan een kopie dacht. Nou dat zou leuk kunnen zijn. Maar ik heb de ruimte snel nodig, dus ik denk niet echt dat ik genoeg tijd heb om te zien of er iets mis is of niet. Bedankt voor de tip. Ik heb het ook gepost in Code Review.
toegevoegd de auteur JasonTS, de bron
Nou, ik heb de tijd om ze naar een andere map te verplaatsen. Maar als het niet erg duidelijk is, denk ik niet dat er een manier is om alle mappen handmatig te controleren. En omdat ik mijn laptop echt moet spoelen, moet ik de map waarin ik de bestanden heb verplaatst, toch verwijderen. Maar afgezien daarvan. Ziet u fouten in mijn code? Of denk je dat dit zou moeten werken?
toegevoegd de auteur JasonTS, de bron
Nou, ik heb de tijd om ze naar een andere map te verplaatsen. Maar als het niet erg duidelijk is, denk ik niet dat er een manier is om alle mappen handmatig te controleren. En omdat ik mijn laptop echt moet spoelen, moet ik de map waarin ik de bestanden heb verplaatst, toch verwijderen. Maar afgezien daarvan. Ziet u fouten in mijn code? Of denk je dat dit zou moeten werken?
toegevoegd de auteur JasonTS, de bron
Helaas is de 3TB NAS vol. Ik heb nog maar 20 GB over, dus ik moet het verwijderen. Trouwens, ik heb het over 139.020 gedupliceerde bestanden. Er is geen manier waarop ik handmatig kan controleren dat het script niet in de war raakte.
toegevoegd de auteur JasonTS, de bron

8 antwoord

Dit zag er goed uit en nadat ik een beetje had schoongemaakt (om het met python 3.4 te laten werken), heb ik dit op mijn NAS uitgevoerd. Hoewel ik hardlinks had voor bestanden die niet tussen back-ups waren gewijzigd, werden bestanden die waren verplaatst, gedupliceerd. Deze herstelde die schijfruimte voor mij verloor.

Een kleine muggenzift is dat bestanden die al bestaan, worden verwijderd en opnieuw worden gekoppeld. Dit heeft hoe dan ook geen invloed op het eindresultaat.

I deed het derde bestand ("3.py") enigszins wijzigen:

if sha1Checksum(path) == original_checksum:
     tmp_filename = path + ".deleteme"
     os.rename(path, tmp_filename)
     os.link(original, path)
     os.unlink(tmp_filename)
     print("Deleted {} ".format(path))

Dit zorgt ervoor dat in het geval van een stroomstoring of een andere soortgelijke fout geen bestanden verloren gaan, hoewel een achterliggend "deleteme" achterblijft. Een herstelscript zou vrij triviaal moeten zijn.

1
toegevoegd

Dit zag er goed uit en nadat ik een beetje had schoongemaakt (om het met python 3.4 te laten werken), heb ik dit op mijn NAS uitgevoerd. Hoewel ik hardlinks had voor bestanden die niet tussen back-ups waren gewijzigd, werden bestanden die waren verplaatst, gedupliceerd. Deze herstelde die schijfruimte voor mij verloor.

Een kleine muggenzift is dat bestanden die al bestaan, worden verwijderd en opnieuw worden gekoppeld. Dit heeft hoe dan ook geen invloed op het eindresultaat.

I deed het derde bestand ("3.py") enigszins wijzigen:

if sha1Checksum(path) == original_checksum:
     tmp_filename = path + ".deleteme"
     os.rename(path, tmp_filename)
     os.link(original, path)
     os.unlink(tmp_filename)
     print("Deleted {} ".format(path))

Dit zorgt ervoor dat in het geval van een stroomstoring of een andere soortgelijke fout geen bestanden verloren gaan, hoewel een achterliggend "deleteme" achterblijft. Een herstelscript zou vrij triviaal moeten zijn.

1
toegevoegd

Waarom vergelijkt u niet de bytes van de bytes in plaats van de tweede controlesom? Eén op een miljard twee controlesommen kunnen per ongeluk overeenkomen, maar directe vergelijking mag niet mislukken. Het moet niet langzamer zijn, en misschien zelfs sneller. Misschien kan het langzamer zijn als er meer dan twee bestanden zijn en moet u het originele bestand voor elkaar lezen. Als je echt zou willen, zou je dat kunnen omzeilen door blokken van alle bestanden in één keer te vergelijken.

BEWERK:

Ik denk niet dat het meer code vereist, alleen maar anders. Iets als dit voor het luslichaam:

data1 = fh1.read(8192)
data2 = fh2.read(8192)
if data1 != data2: return False
1
toegevoegd
Anders ziet het er goed uit, maar ben ik niet bekend met alle API's die je hebt gebruikt. Ik maak een goed geïnformeerde schatting van wat ze doen.
toegevoegd de auteur morningstar, de bron

Waarom vergelijkt u niet de bytes van de bytes in plaats van de tweede controlesom? Eén op een miljard twee controlesommen kunnen per ongeluk overeenkomen, maar directe vergelijking mag niet mislukken. Het moet niet langzamer zijn, en misschien zelfs sneller. Misschien kan het langzamer zijn als er meer dan twee bestanden zijn en moet u het originele bestand voor elkaar lezen. Als je echt zou willen, zou je dat kunnen omzeilen door blokken van alle bestanden in één keer te vergelijken.

BEWERK:

Ik denk niet dat het meer code vereist, alleen maar anders. Iets als dit voor het luslichaam:

data1 = fh1.read(8192)
data2 = fh2.read(8192)
if data1 != data2: return False
1
toegevoegd
Anders ziet het er goed uit, maar ben ik niet bekend met alle API's die je hebt gebruikt. Ik maak een goed geïnformeerde schatting van wat ze doen.
toegevoegd de auteur morningstar, de bron

Hoe creëer je een harde link.

In Linux doe je dat

sudo ln sourcefile linkfile

Soms kan dit mislukken (voor mij faalt het soms). Ook moet uw python-script in de sudo-modus worden uitgevoerd.

Dus gebruik ik symbolische links:

ln -s sourcefile linkfile

I can check for them with os.path.islink

Je kunt de commando's zoals deze in Python bellen:

os.system("ln -s sourcefile linkfile")

of zo met behulp van subproces :

import subprocess
subprocess.call(["ln", "-s", sourcefile, linkfile], shell = True)

Have a look at execution from command line and hard vs. soft links

Wanneer het werkt, zou je dan je hele code kunnen posten? Ik zou het ook willen gebruiken.

0
toegevoegd
Zachte links zijn gevaarlijk voor dit scenario, omdat het verwijderen van één back-up alle andere back-ups die hetzelfde bestand bevatten, zou verbreken. U moet niet ook als root uitvoeren om harde koppelingen te maken.
toegevoegd de auteur WhyNotHugo, de bron
Bedankt! Ik besloot geen softlinks te gebruiken, omdat ik niet weet waar het bestand eigenlijk zou moeten zijn. Ik zal later handmatig opruimen. Maar voor nu heb ik echt ruimte nodig. Met een harde link maakt het niet uit welk 'bestand' ik zal verwijderen. Maar met zachte links kan ik alleen de 'ware bestanden' verwijderen als ik de gegevens wil behouden. Ik denk ook dat sommige van mijn fotobewerkingssoftware niet van zachte links houden. Maar ik denk dat je gelijk hebt, het maken van een link kan mislukken en ik zou een uitzondering moeten maken voor wanneer deze mislukt. Ik hoef geen sudo te gebruiken, omdat ik als root aan het rennen ben. Er is niets anders dan de foto's daar.
toegevoegd de auteur JasonTS, de bron

Hoe creëer je een harde link.

In Linux doe je dat

sudo ln sourcefile linkfile

Soms kan dit mislukken (voor mij faalt het soms). Ook moet uw python-script in de sudo-modus worden uitgevoerd.

Dus gebruik ik symbolische links:

ln -s sourcefile linkfile

I can check for them with os.path.islink

Je kunt de commando's zoals deze in Python bellen:

os.system("ln -s sourcefile linkfile")

of zo met behulp van subproces :

import subprocess
subprocess.call(["ln", "-s", sourcefile, linkfile], shell = True)

Have a look at execution from command line and hard vs. soft links

Wanneer het werkt, zou je dan je hele code kunnen posten? Ik zou het ook willen gebruiken.

0
toegevoegd
Zachte links zijn gevaarlijk voor dit scenario, omdat het verwijderen van één back-up alle andere back-ups die hetzelfde bestand bevatten, zou verbreken. U moet niet ook als root uitvoeren om harde koppelingen te maken.
toegevoegd de auteur WhyNotHugo, de bron
Bedankt! Ik besloot geen softlinks te gebruiken, omdat ik niet weet waar het bestand eigenlijk zou moeten zijn. Ik zal later handmatig opruimen. Maar voor nu heb ik echt ruimte nodig. Met een harde link maakt het niet uit welk 'bestand' ik zal verwijderen. Maar met zachte links kan ik alleen de 'ware bestanden' verwijderen als ik de gegevens wil behouden. Ik denk ook dat sommige van mijn fotobewerkingssoftware niet van zachte links houden. Maar ik denk dat je gelijk hebt, het maken van een link kan mislukken en ik zou een uitzondering moeten maken voor wanneer deze mislukt. Ik hoef geen sudo te gebruiken, omdat ik als root aan het rennen ben. Er is niets anders dan de foto's daar.
toegevoegd de auteur JasonTS, de bron

Opmerking: als je niet met Python getrouwd bent, zijn er uitstekende hulpmiddelen om het zware werk voor je te doen:

https: //unix.stackexchange. com/vragen/3037/is-er-een-easy-way-to-replace-duplicate-files-met-hardlinks

0
toegevoegd

Opmerking: als je niet met Python getrouwd bent, zijn er uitstekende hulpmiddelen om het zware werk voor je te doen:

https: //unix.stackexchange. com/vragen/3037/is-er-een-easy-way-to-replace-duplicate-files-met-hardlinks

0
toegevoegd