[gelöst] Dateinamen - Unicode

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
use_opensue
User
Beiträge: 33
Registriert: Mittwoch 15. August 2007, 10:43

Hallo,
ersteinmal vielen Dank an die Forenmitglieder, ihr habt mir schon öfter geholfen!

Doch hierbei komme ich nicht weiter:

Ich habe ein Skript, das eine Verzeichnisstruktur durchläuft, den aktuellen Ordner ausgibt und zu jeder Datei im Ordner den Namen und die SHA - Prüfsumme ausgibt.
Ausgegeben wird das ganze in eine Datei.

Das Funktioniert soweit. Doch ich habe ein Verzeichnis, in das Streamripper Klassikstreams
schreibt. Dort sind die Dateinamen teilweise schrott, bzw. einzelne Zeichen nicht darstellbar?

Mein System ist OpenSuse 10.2, Encoding ist utf-8.
Hier das Skript: (Die SHA-Summenfunktion stammt auch aus diesem Forum.)

Code: Alles auswählen

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

import codecs
import locale
import os
import sha
import sys

# Funktionsdefinitionen
def shasum(filepath, blocksize = 1024 * 1024):
    

def del_old_indexfile():
    

def walker(a, b, c): # a='haha', b=aktuelles Verzeichniss(String), c=Verzeichnissinhaltsliste von b
    '''Eigentliche Programmlogik, muss noch genauer Dokumentiert werden.
    Hackstatus, aber in der Funktionsweise getestet.'''
    datei = cwd + '/Inhaltsliste.txt'
    datei = os.path.normcase(datei)
    f = codecs.open(datei, 'a', 'utf-8') # keine Probleme mehr mit Umlauten in Dateinamen
    string_b = '\n' + b + '\n\n'
    f.write(string_b)
    for eintrag in c:
        if os.path.isfile(os.path.normcase(b + os.sep + eintrag)) == 1: # Probleme mit ASCII - Unicode Dateinamen!
            sha_summe = shasum(b + os.sep + eintrag)
            f.write('  SHA-Summe: ' + sha_summe + ' Datei: ' + eintrag + '\n')
        else:
            f.write(' Verzeichnis: ' + eintrag + '\n')
    f.close()
    print datei,
    print 'geschrieben.'

def get_file_encoding(f):
    if hasattr(f, "encoding"):
        return f.encoding
    else:
        return "not set"

# Skriptbeginn
print

# Encodings des Systems ausgeben
print "sys.stdin.encoding:", get_file_encoding(sys.stdin)
print "sys.stdout.encoding:", get_file_encoding(sys.stdout)
print "sys.stderr.encoding:", get_file_encoding(sys.stderr)
print "sys.getdefaultencoding():", sys.getdefaultencoding()
print "sys.getfilesystemencoding():", sys.getfilesystemencoding()
print "locale.getpreferredencoding():", locale.getpreferredencoding()

# Ermitteln des aktuellen Arbeitsverzeichnisses cwd = ('c'urrent 'w'orking 'd'ir)
cwd = os.getcwdu() # Wie getcwd() aber in Unicode
print 'aktuelles Arbeitsverzeichniss: %s' % cwd
print

del_old_indexfile()

os.path.walk(cwd, walker, 'haha') # 'haha' muss sein, ansonsten Syntaxfehler

# Skriptende
Die Ausgabe auf der Konsole:

Code: Alles auswählen

vwo-g01@ws08a:~/Streams> python /home/vwo-g01/Programmierung/Python/sha_summe_ver_4.py

sys.stdin.encoding: UTF-8
sys.stdout.encoding: UTF-8
sys.stderr.encoding: UTF-8
sys.getdefaultencoding(): ascii
sys.getfilesystemencoding(): UTF-8
locale.getpreferredencoding(): UTF-8
aktuelles Arbeitsverzeichniss: /home/vwo-g01/Streams

/home/vwo-g01/Streams/Inhaltsliste.txt geschrieben.
/home/vwo-g01/Streams/Inhaltsliste.txt geschrieben.
/home/vwo-g01/Streams/Inhaltsliste.txt geschrieben.
Traceback (most recent call last):
  File "/home/vwo-g01/Programmierung/Python/sha_summe_ver_4.py", line 82, in <module>
    os.path.walk(cwd, walker, 'haha') # 'haha' muss sein, ansonsten Syntaxfehler
  File "/usr/lib/python2.5/posixpath.py", line 298, in walk
    walk(name, func, arg)
  File "/usr/lib/python2.5/posixpath.py", line 298, in walk
    walk(name, func, arg)
  File "/usr/lib/python2.5/posixpath.py", line 290, in walk
    func(arg, top, names)
  File "/home/vwo-g01/Programmierung/Python/sha_summe_ver_4.py", line 49, in walker
    if os.path.isfile(os.path.normcase(b + os.sep + eintrag)) == 1: # Probleme mit ASCII - Unicode Dateinamen!
UnicodeDecodeError: 'ascii' codec can't decode byte 0xba in position 51: ordinal not in range(128)
vwo-g01@ws08a:~/Streams>
Entweder os.path.isfile() haut etwas kaputt oder 'eintrag' hat ein Encodingproblem.
Ich komme hier 'vorerst' nicht mehr weiter.

Ein ls des Ordners gibt die Dateinamen mit ?Chinesischen?-Schriftzeichen durchsetzt aus.
Zuletzt geändert von use_opensue am Montag 20. August 2007, 13:31, insgesamt 1-mal geändert.
BlackJack

Entweder ist `b` eine Unicode-Zeichenkette oder `c` enthält eine solche. Da Dateinamen unter Unix nahezu beliebige Byteketten sein können, ist das vielleicht keine so gute Idee.

Als drittes Argument an `os.path.walk()` 'haha' zu übergeben ist ein wenig albern. Da würde ich wie in der Doku vorgeschlagen `None` verwenden, wenn Du dieses Argument nicht benötigst.

Pfade setzt man am besten mit `os.path.join()` zusammen. Und das Ergebnis von `os.path.isfile()` ist bereits ein Wahrheitswert, den man nicht noch einmal mit 1 vergleichen muss. Was sowieso nur funktioniert weil Wahrheitswerte aus historischen Gründen Untertypen von ganzen Zahlen sind.
use_opensue
User
Beiträge: 33
Registriert: Mittwoch 15. August 2007, 10:43

`b` hat den selben Inhalt wie 'cwd', und sollte unicode sein.
`c` sollte ebenfalls unicode sein.

Dies war bisher noch kein Problem, es lief bei mir über ext2, ext3, reiser3, fat und ntfs(nur lesend) Partitionen und nur im Ordner mit den Klassik-Streams kam es zum Fehler.
Daher meine Annahme, dass Streamripper die Dateinamen 'versaut' hat.

Einen ls des Ordners erspare ich euch.

`None` für `os.path.walk()` ist in meiner Doku(älter, aber Papier!) nicht erwänhnt worden, ist aber auf jeden Fall besser.
`os.path.join()` schau ich mir an.
`os.path.isfile()` war mir so nicht bewusst, den Vergleich mit 1 werde ich ändern.

Im Endeffekt suche ich eine Methode um die Dateinamen als Unicode-Strings zu lesen und zu verarbeiten. Damit sollte es dann keine Probleme mehr geben, egal wie diese ehemals kodiert wurden, oder?
BlackJack

use_opensue hat geschrieben:Im Endeffekt suche ich eine Methode um die Dateinamen als Unicode-Strings zu lesen und zu verarbeiten. Damit sollte es dann keine Probleme mehr geben, egal wie diese ehemals kodiert wurden, oder?
Nein, gerade da können Probleme auftauchen. Da Dateinamen bei Unix Byteketten sind und nicht mitgespeichert wird in welcher Kodierung die vorliegen, kann man durchaus Dateinamen in verschiedenen Kodierungen auf der Platte haben. Ich würde Dateinamen als Bytes belassen und verarbeiten.

Mal so nebenbei, kann es sein, dass in Deinem Namen ein 's' fehlt? Oder wer ist Sue? :-)
use_opensue
User
Beiträge: 33
Registriert: Mittwoch 15. August 2007, 10:43

Ja, Ja, fällt doch auf, aber jetzt bleibe ich dabei! use_opensuse wär ja auch zu einfach ;)

Wenn ich Dateinamen als Byteketten behandle und verarbeite, können dann noch irgendwelche Fehler damit auftreten? Bzw. ist das Plattformunabhängig?(Eigentlich schon...)

Jetzt muss ich nur noch darüber Nachdenken wie ich das Umsetze.

Vielen Dank auf jeden Fall! Mit so schnellen Antworten hätte ich nicht gerechnet.

Gute Nacht. Oder besser Guten Morgen :)
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Die Dateinamen als Zeichenketten anzusehen Funktioniert solange du sie nicht anzeigen oder übers Netz schicken willst eigentlich ganz gut ;)
use_opensue
User
Beiträge: 33
Registriert: Mittwoch 15. August 2007, 10:43

So, nun kann das Skript mit folgenden Dateinamen umgehen:

Testdatei_003_.,-+!()=.txt
Testdatei_002_ÄÖÜ_äöüß.txt
Testdatei_001_ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz_0123456789.txt
Testdatei_004_()[]{}%2f\ß%%$§"!^°*'~+#<>|.txt

Woran es lag:

Code: Alles auswählen

f = codecs.open(datei, 'a', 'utf-8')
warf mit 2 der obigen Dateinamen bei:

Code: Alles auswählen

dzk = '  SHA-Summe: ' + sha_summe + ' Datei: ' + eintrag + '\n'
f.write(dzk)
Diesen Fehler:

Code: Alles auswählen

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 75: ordinal not in range(128)
Die (wechselnden) Dateinamen stehen in

Code: Alles auswählen

eintrag
Warum wirft

Code: Alles auswählen

f = open(index_datei, 'a')
diesen Fehler nicht?
use_opensue
User
Beiträge: 33
Registriert: Mittwoch 15. August 2007, 10:43

Hier einmal das ganze Skript:

Code: Alles auswählen

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

# ----- Lizenz: GPL Version 3 -----

# ----- Modulimporte: -----

import os
import sha

# ----- Funktionsdefinitionen: -----

def shasum(filepath, blocksize = 1024 * 1024):
    """
    Gibt die SHA-Checksumme (Hex-Digest) der angegebenen Datei zurück.
    filepath: Pfad zu einer Datei
    blocksize: Größe des Datenblocks, der auf einmal bearbeitet werden soll.
    """
    h = sha.new()
    f = open(os.path.normcase(filepath), "rb", )
    # normcase() vereinheitlicht die Pfade, unter Unix bleibt alles gleich,
    # unter windows wird alles kleingeschrieben und Slashes(/) in
    # Backshlashes(\) gewandelt.
    try:
        while True:
            part = f.read(blocksize)
            if not part:
                break
            h.update(part)
    finally:
        f.close()
   
        return h.hexdigest()

def del_indexfile():
    '''Löschen der Inhaltsliste, falls sie existiert, damit der evtl.
    veränderte Verzeichnissinhalt nich an die alte Datei angehangen wird'''
    if os.path.exists(index_datei):
        os.remove(index_datei)
        print 'Alte', index_datei, ' gelöscht.'
        print

# a='None', b=(type=String)aktuelles Verzeichniss
# c=(type=List) Verzeichnissinhaltsliste von b

def walker(a, b, c):
    '''Legt im aktuellen Pfad die Datei "Inhaltsliste.txt" an.
       Durchwandert alle Unterverzeichnisse und schreibt das jeweilige
       Verzeichniss(voller Pfad), sowie alle darin enthaltenen Dateien(mit
       der zugehörigen SHA-Prüfsumme) in die Inhaltsliste.'''
    f = open(index_datei, 'a')
    
    # 'a'ktueller'o'rdner'z'eichen'k'ette
    aozk = '\n' + b + '\n\n'
    f.write(aozk)
    for eintrag in c:
        eintrag_u_pfad = os.path.normcase(os.path.join(b, eintrag))
        if os.path.isfile(eintrag_u_pfad):
            sha_summe = shasum(eintrag_u_pfad)
            
            # 'd'atei'z'eichen'k'ette
            dzk = '  SHA-Summe: ' + sha_summe + ' Datei: ' + eintrag + '\n'
            f.write(dzk)
        else:
            
            # 'v'erzeichnis'z'eichen'k'ette
            vzk = ' Verzeichnis: ' + eintrag + '\n'
            f.write(vzk)
    f.close()

# Skriptbeginn
print

# Ermitteln des aktuellen Arbeitsverzeichnisses cwd = ('c'urrent 'w'orking 'd'ir)
cwd = os.getcwd() # getcwdu() würde dies in Unicode liefern
print 'aktuelles Arbeitsverzeichniss: %s' % cwd
print

index_datei = os.path.normcase(os.path.join(cwd, 'Inhaltsliste.txt')) # join() verknüpft Pfade inteligent.
del_indexfile()
os.path.walk(cwd, walker, 'None') # 'None' muss sein, ansonsten Syntaxfehler
print 'Neue ', index_datei, 'geschrieben.'

# Skriptende
BlackJack

Da sind einige Namen die zu kurz und zu kryptisch sind. Die Abkürzung in einem Kommentar zu erklären macht es auch nicht viel besser.

`del_indexfile()` sollte den Dateinamen als Argument bekommen. Globale Namen sind "böse". Und das Testen ob die Datei existiert, würde ich durch Ausnahmebehandlung ersetzen.

Die Argumente von `walker()` sollten aussagekräftigere Namen als `a`, `b` und `c` bekommen. Eine offene Datei wäre auch ein guter Kandidat für das zusätzliche Argument. Dann muss die Datei nicht ständig geöffnet und geschlossen werden. Das verbraucht sicher einiges an Zeit, weil beim schliessen auch immer ein `flush()` gemacht wird, und man so einen Geschwindigkeitsgewinn durch Pufferung verhindert. Mit `None` meinte ich übrigens auch das *Objekt* `None` und nicht eine Zeichenkette die das Wort 'None' enthält.

Zum Problem: Eine mit `codecs.open()` geöffnete Datei erwartet Unicode-Zeichenketten. Wenn man eine normale Zeichenkette übergibt, wird die vorher dekodiert und dann in der entsprechenden Kodierung geschrieben. Zum dekodieren wird ASCII genommen, d.h. wenn die Zeichenkette Bytewerte ausserhalb von 0 bis 127 enthält, dann kracht es.
use_opensue
User
Beiträge: 33
Registriert: Mittwoch 15. August 2007, 10:43

Das die kurzen Namen von Nachteil sind ist mir heute schon aufgegengen, ich habe ein altes Skript gelesen und jedesmal den erklärenden Kommentar gesucht wenn so eine Abkürzung vorkam. Das tue ich mir und evtl. Anderen nie wieder an.

Warum sind globale Namen böse?

Die walker() Funktion entfällt komplett, da sichdas ganze mit einer for-Schleife und os.walk() anstelle von os.path.walk() schöner programmieren lässt. Dabei entfällt auch das ständige Öffnen und Schließen der Datei.

Ich bin auch von codecs.open() weg und nutze einfach open(), das funktioniert auch mit dem Klassikstream-Ordner, in dem die Dateinamen versaut sind.

Ich werde noch ein bisschen am an der aktuellen Version feilen und dise dann hier veröffentlichen.

Für mich ist die Geschichte damit gelöst. Esseidenn es kann auch mit

Code: Alles auswählen

f = open(index_datei, 'w')
bei 'verkrüppelten' Dateinamen zu Fehlern kommen.


Vielen Dank für die Hilfe!!
Benutzeravatar
Damaskus
Administrator
Beiträge: 995
Registriert: Sonntag 6. März 2005, 20:08
Wohnort: Schwabenländle

Thread gesplittet und nach Codesnippets verschoben da das Antworten hier nicht mehr möglich war.

http://www.python-forum.de/viewtopic.php?p=75098#75098


Gruß
Damaskus
BlackJack

@use_opensue: Globale Namen sind "böse" weil damit eine Funktion von etwas ausserhalb abhängig ist, das nicht über die Argumente hinein gelangt. Das macht den Quelltext unübersichtlicher weil man sich fragen muss wo bestimmte Namen eigentlich herkommen und wie und wo sie gesetzt werden. Bei Argumenten kann man das recht einfach herausfinden indem man den Aufruf der Funktion sucht.

So etwas fällt auch unangenehm auf, wenn man zuviel in einem Modul hat und den Quelltext auf mehrere Module aufteilen will. Wenn die Daten durch Funktionen nur als Argumente und Rückgabewerte geschleust werden, kann man die Funktion ganz einfach in ein anderes Modul verschieben und im ursprünglichen Modul importieren. Wenn die Funktion aber von globalen Namen im alten Modul abhängig ist, wird es kompliziert.

Argumente sind auch flexibler weil man die Funktion von verschiedenen Stellen mit verschiedenen Argumenten aufrufen kann. Aus der speziellen Löschfunktion für den Index kann man zum Beispiel ganz einfach eine machen, die alle möglichen Dateien ohne Fehlermeldung löscht.
use_opensue
User
Beiträge: 33
Registriert: Mittwoch 15. August 2007, 10:43

@Damaskus: Danke für das Threadtrennen, ich dachte es wär ein Serverfehler und kein Softwarefehler. Ich kenne zwar ein paar paste-Services, doch da dort die Daten irgendwann verfallen, bedeutet die Nutzung dieser Services einen Schleichenden Informationsverlust, den ich nicht in kauf nehmen möchte


So, ich habe das Skript nochmals überarbeitet, die aktuelle Version hänge ich an den abgetrennten Threadteil an.

Version 1.5 braucht um 17864 Dateien in 857 (Unter-)Ordnern mit einer Größe von 26,1 GB zu verarbeiten 16 Minuten.

Nochmals Danke!
Antworten