Hashwert einer Datei berechnen und Fortschritt zurückgeben

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Hi Community,

Ich möchte den Hashwert einer Datei berechnen, und während dieser Berechnung den Fortschritt der Hashwert Berechnung angeben.

Das ganze bringt mehrere Probleme mit sich:
Bevor ich mit der Berechnung beginnnen kann, muss ich erstmal wissen wie viel lines die Datei hat. Dies macht die Berechnung sehr langsam.

Hier mein Code, er funktioniert, ist aber in keinster Weise effizient:

Code: Alles auswählen

checksum = hashlib.sha224()

with open(filepath, "rb") as f:
    lines =  f.readlines()  # Dauert Lange :P
    line_length = len(lines)
    counter = 0      
    print "Starte"
    for line in f: # for line in lines macht hier das selbe oder?
        checksum.update(line)
        # Informiere Nutzer über Fortschritt
        counter += 1
    print "Ende"
f.close()
Hat jemand einen Vorschlag für eine andere Art und Weise wie man an dieses Problem ran geht? Vielleicht das ganze mit der Dateigröße abschätzen?

Grüße,
anogayales
Warhead
User
Beiträge: 15
Registriert: Montag 28. Juni 2010, 12:54

Hi anogayales,

Du könntest z.B. mittels os.path.getsize(<path>) die Größe in Byte ermitteln und danach via "<file>.read(<byte>)" die Datei Häppchenweise auslesen. Wenn Du die Häppchen so groß wählst, dass sie 1/100tel der Gesamtdateigröße entsprechen, dann hast Du damit auch eine erstmal essentielle, generische Basis für die Fortschrittsbestimmung/Anzeige.

Optimierung kann danach immer noch betrieben werden ;).
When altering one's own mind becomes as easy as programming a computer… What does it mean to be human?
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Unter Debian und ähnlichen GNU/Linux Systemen könntest du den effizienten pv Befehl benutzen, hier mit md5sum:

Code: Alles auswählen

pv <file> | md5sum
Statt md5sum könntest du hier auch ein kleines Skript benutzen, welches dir den Hashwert im entsprechendem Format ausgibt (bei dir sha224) oder einen anderen, eingebauten Befehl benutzen. Um Warheads Idee mittels iter()s speziellem Verhalten zu implementieren:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import hashlib
from functools import partial

def main():
    def accum(checksum, chunk):
        checksum.update(chunk)
        return checksum
    print reduce(accum, iter(partial(sys.stdin.read, 1024), ""), hashlib.sha224()).hexdigest()

    return 0

if __name__ == "__main__":
    sys.exit(main())
Wahlweise natürlich ohne den etwas ineffizienten reduce() Aufruf, partial() oder diese spezielle Möglichkeit, iter() zu nutzen.

Code: Alles auswählen

str1442:~$ cat > example_text << EOF
> Dies ist ein Beispieltext.
> EOF
str1442:~$ python sha224.py < example_text 
0611fed92052d649d00b6f0c862ae9f5d953636ebc15ed334b185f88
str1442:~$ pv example_text | python sha224.py 
  27B 0:00:00 [ 326kB/s] [===============================================================================================================>] 100%            
0611fed92052d649d00b6f0c862ae9f5d953636ebc15ed334b185f88
Für die Zeilenanzahl dann das wc Kommando, aber die brauchst du ja sowieso nur für die Fortschrittberechnung der Operation. Solltest du eine Lösung wünschen, die nur in Python geschrieben ist (vllt. falls du Windows benutzt), gibt es für Fortschrittsbalken auch irgendwo ein Modul in http://pypi.python.org/ . Dem müsstest du dann für jeden "chunk" die angegebene Grösse und vorher die Gesamtgrösse der Datei (os.stat()) übermitteln.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Vielen Dank für eure bisherigen Antworten.

Das ganze soll aber in einer GUI laufen (Qt). Demnach darf eigentlich nichts auf der Konsole angezeigt werden. Zusätzlich muss das ganze auch auf Windows laufen, dennoch vielen Dank!

Grüße,
anogayales
BlackJack

@anogayales: Unabhängig vom GUI-Toolkit kann man das einfach mit einer Callback-Funktion machen. Einfach wie schon vorgeschlagen die Dateigrösse erfragen und dann in Blöcken verarbeiten und nach jedem Block die aktuell verarbeitete Bytezahl per Callback melden. Was die Callback-Funktion dann damit macht -- per ``print`` ausgeben oder in einer GUI darstellen -- muss die Datei-Hash-Funktion ja nicht wissen.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Also ich hab das ganze mal sehr naiv implementiert, bei mir werden aber noch ein paar Bytes bei der Berechnung ausgelassen. Liegt wohl an der Integerdivision:

Code: Alles auswählen

checksum = hashlib.sha224()           

filesize = os.path.getsize(filepath)
stepsize = filesize/100

print "The file size is %s bytes" % filesize
print "The stepsize is %s bytes" % stepsize
print "The error is %s" % (filesize - stepsize * 100)

with open(filepath, "rb") as f:
    counter = 0
    for line in f.read(stepsize):
        while(True):
            checksum.update(line)
            counter += stepsize
            if counter >= filesize:
                break
            f.seek(counter)
        # Calculate remainder and also use it to calculate the hash value 
        remainder = filesize - f.tell()
        print "The remainder is %s" % remainder
        checksum.update(f.read(remainder))
        print "Current position %s" % f.tell() 
        print "The file size is %s bytes" % filesize       
        break
            
        
f.close()
Hat da jemand noch diverse Tricks im Ärmel? :)

Wenn ich die Bedingung if counter >= filesize: rausnehme läuft mir das Ding ins unendliche. Ich hab immer gedacht eine Datei sei endlich :P Komisch, dass er mir da keine Fehlermeldung ausspuckt?!?

Leider bekomme ich damit nicht den gleichen Hashwert wie bei der obigen Referenzimplementierung raus. Sollte halt schon bezüglich des Ergebnisses invariant sein.

Grüße,
anogayales
BlackJack

@anogayales: Ich würde davon abraten die Blockgrösse zu berechnen. Wenn die Datei <100 Bytes ist kommt da 0 heraus, das ist sicher lustig. ;-) Wenn sie >100 aber immer noch klein ist, werden in jedem Schleifendurchlauf nur wenige Bytes gelesen, das ist unsinnig.

Das ist auch extrem verwirrend ausgedrückt mit dem `seek()`. Lies einfach in einer Schleife solange Blöcke mit einer festen Grösse bis keine Daten mehr kommen. Dann sparst Du Dir auch irgendwelche Spässe mit dem berechnen von der Blockgrösse und dem letzten Block. Da stimmt wahrscheinlich auch irgendetwas nicht, denn wenn Du die ``while``-Scheife verlässt, dann ist `counter` ja schon grösser oder gleich der Dateigrösse, also ist zu dem Zeitpunkt schon alles verarbeitet.

Die Schleife würde ohne diesen Test ewig weiterlaufen, weil ein `read()` am Ende einer Datei eine leere Zeichenkette liefert. Immer und immer wieder. *Das* ist aber auch ein prima Abbruchkriterium.

Du darfst den `counter` auch nicht einfach um `stepsize` erhöhen, denn ein `read(stepsize)` kann auch weniger als `stepsize` Bytes liefern -- nämlich am Ende der Datei wenn gar nicht mehr genug Bytes da sind, oder eben ganz am Ende wenn immer 0 Bytes gelesen werden.

Code: Alles auswählen

import os
from functools import partial
from hashlib import sha224

def hash_file(file_obj,
              hasher,
              callback=lambda byte_count: None,
              blocksize=4096):
    
    byte_count = 0
    for block in iter(partial(file_obj.read, blocksize), ''):
        hasher.update(block)
        byte_count += len(block)
        callback(byte_count)
    return hasher.hexdigest()


def main():
    filename = 'test.iso'
    filesize = os.path.getsize(filename)
    
    def print_progress(byte_count):
        print '\r%d/%d %6.2f' % (byte_count,
                                 filesize,
                                 100.0 * byte_count / filesize),
    
    with open(filename) as iso_file:
        print hash_file(iso_file, sha224(), print_progress)
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

Vielen Dank für deine ausführliche Darlegung!

EDIT:
HABS! Man muss die Datei, in meinem Fall mit open(filename, "rb"). Sonst berechnet es was anderes, nur was? :)

Frage:
Gibt es eigentlich eine Möglichkeit mit partial den zusätlichen parameter VOR die anderen Parameter zu schieben und nicht dahinter?

Grüße,
anogayales
Antworten