Python Script zum ändern von Dateiendungen

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.
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

hi zusammen,

endlich hab ichs geschafft, mein erstes selbst gebautes python script :D
und es funktioniert auch so wie ich es mir vorstelle, ich würde nur gern wissen ob ihr das so ok findet oder ob ihr verbesserungsvorschläge habt, ich will ja was lernen :)


import os, glob, string
p = raw_input("Bitte den vollständigen Pfad eingeben: ")
if os.path.exists(p):
e = raw_input("Welche Dateiendung, z.B.: .txt, soll geändert werden? ")
n = raw_input("Wie lautet die neue Dateiendung, z.B.: .jpg? ")
print(p, " : Pfad existiert!")
os.chdir(p)
filenames = glob.glob('*' + e)
print("Folgende Dateien wurden gefunden:")
for file in filenames:
print(file)
i = 0
for filename in filenames:
newfilename = string.replace(filename, e, n)
os.rename(filename, newfilename)
i = i + 1
print(i, "Dateien wurden umbenannt.")
else:
print(p, " : Pfad existiert nicht!")

gruß
korkak

ps: irgendwie übernimmt der die formatierung von den einrückungen nicht...
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

ps: irgendwie übernimmt der die formatierung von den einrückungen nicht...
Das liegt daran, dass du deinen Code ausdrücklich in python-Tags schreiben musst.
  • `os.chdir(p)` ganz böse! ;-) Man erwartet von einem Skript nicht, dass es einfach das Verzeichnis wechselt, wenn man es aufruft.
  • Du kannst dir auch mal `os.walk` anschauen.
  • `string.replace(filename, e, n)` Da ist ein logischer Fehler drin. Da wird nicht nur die Dateiendung geändert … Außerdem solltest du lieber filename.replace(e, n) benutzen. Und wenn du eine Variable sowieso nur einmal verwendest, kannst du den Wert gleich der benutzenden Funktion übergeben.
  • Bei Python 2 ist `print` keine Funktion, sondern ein Statement! Du solltest `print` auch so behandeln, denn das sieht komisch aus:

    Code: Alles auswählen

    >>> print('foo', 'bar')
    ('foo', 'bar')
    >>> 
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich würde es vielleicht so machen:

Code: Alles auswählen

import glob, os, sys

verbose = "-v" in sys.argv[:1]
if verbose:
    del sys.argv[0]

try:
    path, old_ext, new_ext = sys.argv
except ValueError:
    print "usage: rename [-v] <path> <old_ext> <new_ext>"
    sys.exit(1)

if not os.path.exists(path):
    print "rename: %s doesn't exist" % path
    sys.exit(1)

names = glob.glob(os.path.join(path, "*" + old_ext))

for name in names:
    new_name = name.replace(old_ext, new_ext)
    os.rename(name, new_name)
    if verbose:
        print "rename: %s -> %s" % (name, new_name)

if verbose:
    print "rename: %d files renamed" % len(names)
Bei einem Script erwarte ich nicht, dass es mich nach Informationen fragt, sondern dass ich Parameter per Kommandozeile übergebe. Ich halte mich an die Konvention, einen Wert ungleich 0 im Fehlerfalle zurückzugeben und dem Benutzer einen Hinweis für die korrekte Benutzung meiner Funktion zu zeigen (der eigentlich auf stderr und nicht stdout erscheinen sollte). Statt das Verzeichnis zu wechseln, packe ich den Pfad lieber mit in den Aufruf von glob. Da mir dieser eine Liste von Pfaden liefert, muss ich nicht selbst zählen, wie viele das sind. Das Ausgeben der Namen vor dem Umbenennen fand ich nicht sinnvoll, denn habe ich an dieser Stelle einen Fehler gemacht, kann ich eh das Script nicht schnell genug abbrechen. Ich habe daher ein "-v" für "verbose", also geschwätzig als Option eingeführt und entscheide damit, ob ich etwas ausgegeben haben will oder nicht. Bei der rename-Methode ist zu beachten, dass ich möglicherweise auch innerhalb des Pfads und nicht nur am Ende ersetze. Das ist ein Fehler, aber ich wollte jetzt nicht eine Suche mit regulären Ausdrücken einführen. Mein Tipp wäre, mal "endswith" als String-Methode anzuschauen und dann zu überlegen, wie man wohl einen neuen String ohne die alte Endung aber mit der neuen Endung bauen kann, wenn man die Länge des alten Strings kennt.

Stefan
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

nomnom hat geschrieben:
ps: irgendwie übernimmt der die formatierung von den einrückungen nicht...
Das liegt daran, dass du deinen Code ausdrücklich in python-Tags schreiben musst.
  • `os.chdir(p)` ganz böse! ;-) Man erwartet von einem Skript nicht, dass es einfach das Verzeichnis wechselt, wenn man es aufruft.
  • Du kannst dir auch mal `os.walk` anschauen.
  • `string.replace(filename, e, n)` Da ist ein logischer Fehler drin. Da wird nicht nur die Dateiendung geändert … Außerdem solltest du lieber filename.replace(e, n) benutzen. Und wenn du eine Variable sowieso nur einmal verwendest, kannst du den Wert gleich der benutzenden Funktion übergeben.
  • Bei Python 2 ist `print` keine Funktion, sondern ein Statement! Du solltest `print` auch so behandeln, denn das sieht komisch aus:

    Code: Alles auswählen

    >>> print('foo', 'bar')
    ('foo', 'bar')
    >>> 

hi nomnom, danke für deine anmerkungen.

a) os.walk: leider habe ich keine ahnung wie ich das einbauen sollte anstelle von os.chdir.
b) filename.replace: das hab ich geändert und funktioniert, siehe unten.
c) welche variable meinst du, die ich direkt an die funktion übergeben soll weil ich sie nur einmal nutze?
d) was mache ich bei dem print befehl falsch, er scheint mir korrekt zu funktionieren, benutze gerade python 2.4.4 ?

entschuldige meine unwissenheit, befasse ich mich erst seit ca. einer woche mit programmierung bzw. python.

gruß korkak

Code: Alles auswählen

import os, glob, string
p = raw_input("Bitte den vollständigen Pfad eingeben: ")
if os.path.exists(p):
    e = raw_input("Welche Dateiendung, z.B.: .txt, soll geändert werden? ")
    n = raw_input("Wie lautet die neue Dateiendung, z.B.: .jpg? ")
    print(p, " : Pfad existiert!")
    os.chdir(p)
    filenames = glob.glob('*' + e)
    print("Folgende Dateien wurden gefunden:")
    for file in filenames:
        print(file)
    i = 0
    for filename in filenames:
        newfilename = filename.replace(e, n)
        os.rename(filename, newfilename)
        i = i + 1
    print(i, "Dateien wurden umbenannt.")
else:
    print(p, " : Pfad existiert nicht!")
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

@stefan, auch dir ein großes danke, du hast das programm ja gleich neu geschrieben :D

allerdings stehen da soviele dinge drin die ich noch nicht kenne wie sys.argv, except valueerror und vor allem diese %d und %s verstehe ich nicht, da brächte ich noch ein paar extra kommentare was du da genau machst :oops:
ich werde mir das später nochmal ganz genau durchlesen und die informationen zu den funktionen die du da benutzt raussuchen.

danke und gruß
korkak
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

korkak hat geschrieben:a) os.walk: leider habe ich keine ahnung wie ich das einbauen sollte anstelle von os.chdir.
Ich dachte eher an os.walk statt glob.glob, aber ist eigentlich egal.
c) welche variable meinst du, die ich direkt an die funktion übergeben soll weil ich sie nur einmal nutze?
Es gibt nur eine … `newfilename`
d) was mache ich bei dem print befehl falsch, er scheint mir korrekt zu funktionieren
`print` ist keine Funktion. Schau dir an, wie sma `print` benutzt.
benutze gerade python 2.4.4 ?
Python 2.4 ist soweit ich weiß veraltet. Hat es irgendeinen speziellen Grund warum du Python 2.4 benutzt? Sonst solltest du Python 2.7 nutzen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich würde mir such mal smas Vorschlag angucken; da kannst Du einiges von lernen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

sys.argv ist eine Liste mit den Parametern, die einem Python-Programm in der Kommandozeile übergeben wurden. Meine nächste Zeile mit dem "in"-Operator macht sich zu nutze, dass der Teillisten-Operator [:1] niemals fehlschlägt und immer eine Teilliste liefert, ggf. eine leere Liste. In der Teilliste bestehend aus dem ersten Element von sys.argv oder eben der leeren Liste suche ich nach dem String "-v". Wenn er da ist, entferne ich das erste Element der Liste.

Mit try/except kann ich auf einen Fehler reagieren, den Python ansonsten melden würde. Wenn sys.argv nicht drei Elemente enthalt, kommt es zu einem ValueError bei der Dreifachzuweisung. An dieser Stelle will ich aber nicht, dass mein Programm mit dem Fehler abbricht, sondern einen Fehlertext ausgeben und selbst abbrechen. das kann man so lesen: Liebes Python, versuche mal die Zeile(n) hinter try: auszuführen. Geht das mit dem angegebenen Fehler schief, dann führe stattdessen die Zeile(n) hinter dem passenden except-Block aus (davon kann es mehrere geben). Ansonsten geht es nach dem letzten except-Block weiter.

Mit %s und dem "%"-Operator kann ich in einem String andere Strings einfügen. Dort wo "%s" wird dies durch die String-Repräsentation der hinter "%" aufgeführten Variable ersetzt. Da das ein String ist, ist er seine eigene String-Repräsentation, d.h. aus "rename: %s doesn't exist" und einer Variable path mit dem Wert "foo.txt" würde der String "rename: foo.txt doesn't exist" werden.

Gleiches gilt für %d, nur das erwartet eine Zahl, kein String.

Stefan
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

@Stefan

jetzt habe ich einigermaßen verstanden wies das mit sys.argv funktioniert, es wird genutzt, wenn ich das script mit argumenten aufrufen will z.b. unter unix.

was ich vorhabe ist, weiter an diesem script zu arbeiten und lernen was noch möglich ist.
ich möchte es z.b. unter windwos starten können auf dem kein python installiert ist und es dazu zu einer .exe datei umwandeln. dann, falls es möglich ist, möchte ich einbauen das ein fenster(nicht dos) aufgeht, wo ich dann die infos zu pfad und dateieindungen eingeben kann.

sys.exit(1) habe ich jetzt drin falls der pfad nicht existiert.

ich habe auch versucht endswith einzubauen,funktionierte auf den ersten blick, dann habe ich eine datei mit namen "test.txt.date.txt" hinzugefügt um zu überpüfen ob es auch im negativfall funktioniert, doch leider bekomme ich dann die fehlermeldung:
WindowsError: [Error 2] Das System kann die angegebene Datei nicht finden.

Code: Alles auswählen

import os, glob, sys

p = raw_input("Bitte den vollständigen Pfad eingeben: ")

if not os.path.exists(p):
    print "%s Verzeichnis existiert nicht." % p
    sys.exit(1)

alt = raw_input("Welche Dateiendung, z.B.: .txt, soll geändert werden? ")
neu = raw_input("Wie lautet die neue Dateiendung, z.B.: .jpg? ")

if p.endswith(alt):
    names = glob.glob(os.path.join(p, "*" + alt))

print "Folgende Dateien wurden gefunden:"
print "---------------------------------" 
for file in names:
    print file
    
for file in names:
    new_name = file.replace(alt, neu)
    os.rename(file, new_name)

print "-----------------------------"    
print "%d Dateien wurden umbenannt." % len(names)


Benutzeravatar
darktrym
User
Beiträge: 785
Registriert: Freitag 24. April 2009, 09:26

os.path.join fehlt
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
lunar

@sma: Dein Beispiel verarbeitet die Argumente fehlerhaft. In "sys.argv[0]" steht der Name des Programms, nicht das erste Argument. Ist "verbose" falsch, enthält "path" mithin den Programmnamen. Ist "verbose" wahr, enthält "path" die Zeichenkette '-v'. Beides ist wohl kaum gewünscht. Ein gutes Beispiel, warum man lieber die Mittel der Standardbibliothek nutzt:

Code: Alles auswählen

from argparse import ArgumentParser

parser = ArgumentParser()
parser.add_argument('-v', action='store_true', dest='verbose')
parser.add_argument('path')
parser.add_argument('old_ext')
parser.add_argument('new_ext')
args = parser.parse_args()

if not os.path.exists(args.path):
    parser.error('%s does not exit' % path)    

names = glob.glob(os.path.join(args.path, "*" + args.old_ext))

for name in names:
    new_name = name.replace(args.old_ext, args.new_ext)
    os.rename(name, new_name)
    if args.verbose:
        print "rename: %s -> %s" % (name, new_name)

if args.verbose:
    print "rename: %d files renamed" % len(names)
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

darktrym hat geschrieben:os.path.join fehlt

meinst du das in bezug auf die nicht funktionierende funktion:

Code: Alles auswählen

if p.endswith(alt):
    names = glob.glob(os.path.join(p, "*" + alt))
ich glaube eher ich benutze dieses endswith falsch...
BlackJack

@korkak: Du benutzt das an der völlig falschen Stelle. `p` ist der Pfad den der Benutzer eingegeben hat. Welchen Sinn sollte das machen *den* darauf zu prüfen ob er mit der alten Dateieendung endet!?
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

BlackJack hat geschrieben:@korkak: Du benutzt das an der völlig falschen Stelle. `p` ist der Pfad den der Benutzer eingegeben hat. Welchen Sinn sollte das machen *den* darauf zu prüfen ob er mit der alten Dateieendung endet!?
BlackJack, ich wollte sichergehen, das wirklich nur dateien mit der endung z.b. "txt" geändert werden und nicht welche, die zufällig im namen ein "txt" haben. dies wollte ich mit endswith sicherstellen, offensichtlich benutze ich es falsch oder an der falschen stelle, kannst du mir einen tipp geben wie ich es richtig einsetze?

danke und gruß
korkak
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

BlackJack hat dir dir Lösung doch bereits geschrieben. Du hast in ``p`` den eingegebenen Pfad gespeichert, der endet natürlich nicht mit der richtigen Dateiendung. Du bist an dem Ergebnis von ``glob.glob`` interessierst, da die darin enthaltenden Elemente eine bestimmte Endung haben sollen. Das brauchst du aber nicht zu prüfen, da ``glob.glob`` das selber bereits macht.

Sebastian
Das Leben ist wie ein Tennisball.
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

sorry, da habt ihr natürlich vollkommen recht, jetzt verstehe ich es.
mit glob.glob suche ich mir ja eigentlich schon die richtigen dateien raus.

Code: Alles auswählen

names = glob.glob(os.path.join(p, "*." + alt))
unwahrscheinlich aber dennoch möglich wäre aber doch z.b. folgende situtation:
wenn diese dateien im verzeichnis wären:

test.txt
test2.txt
test.txt.nocheintest.txt

dann würde ich die datei "test.txt.nocheintest.txt" z.b. ändern in "test.jpg.nocheintest.jpg" und das wäre ja nicht in ordnung.
deswegen wollte ich die funktion endswith irgendwie einbauen, so das wirklich nur die endung geändert wird, wie bekommt man das hin?

danke und gruß
korkak
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Das ist aber ein Problem des Umbenennens und nicht des Sicherstellens, ob deine Datei tatsaechlich in '.txt' endet, denn das macht `glob` schon.

Hier ein kleiner Trace als Anregung für ein passendes Umbenennen:

Code: Alles auswählen

In [1]: import os

In [2]: name = 'test.txt.nocheintest.txt'

In [3]: os.path.splitext(name)
Out[3]: ('test.txt.nocheintest', '.txt')

In [4]: basename, _ = os.path.splitext(name)

In [5]: new_extension = '.jpg'

In [6]: new_name = basename + new_extension

In [7]: new_name
Out[7]: 'test.txt.nocheintest.jpg'
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

@cofi

ich habe versucht deinen vorschlag umzusetzen und es funktioniert offensichtlich auch, seht ihr vielleicht sonst noch einen fehler/problem?

Code: Alles auswählen

import os, glob, sys

p = raw_input("Bitte den vollständigen Pfad eingeben: ")

if not os.path.exists(p):
    print "Das Verzeichnis '%s' existiert nicht." % p
    sys.exit(1)

alt = raw_input("Welche Dateiendung, z.B.: 'txt', soll geändert werden? ")
neu = raw_input("Wie lautet die neue Dateiendung, z.B.: 'jpg'? ")

names = glob.glob(os.path.join(p, "*." + alt))

print "Folgende Dateien wurden gefunden:"
print "---------------------------------"

for file in names:
    print file

for file in names:
    basename, ext = os.path.splitext(file)
    new_name = basename + "." + neu
    os.rename(file, new_name)
        
print "-----------------------------"    
print "%d Dateien wurden umbenannt." % len(names)

danke und gruß
korkak
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Problem: Du benutzt immer noch nicht die [ python]-Tags. :P Die globalen Variablen könntest du IMHO auch loswerden und das eher in Funktionen auslagern, sonst fragt man sich plötzlich wo z. B. ``neu`` herkommt.

Code: Alles auswählen

for file in names:
    print file
könntest du durch ``print "\n".join(names)`` verkürzen, und die andere ``for``-Schleife könnte man IMHO auch kürzer gestalten:

Code: Alles auswählen

for x in name:
    os.rename(x, os.path.splitext(x)[0]+'.'+neu)
korkak
User
Beiträge: 29
Registriert: Freitag 30. Dezember 2011, 19:44

nomnom hat geschrieben:Problem: Du benutzt immer noch nicht die [ python]-Tags. :P Die globalen Variablen könntest du IMHO auch loswerden und das eher in Funktionen auslagern, sonst fragt man sich plötzlich wo z. B. ``neu`` herkommt.

Code: Alles auswählen

for file in names:
    print file
könntest du durch ``print "\n".join(names)`` verkürzen, und die andere ``for``-Schleife könnte man IMHO auch kürzer gestalten:

Code: Alles auswählen

for x in name:
    os.rename(x, os.path.splitext(x)[0]+'.'+neu)
Hey, danke das du dir noch mal die mühe gemacht hast :P

ich habe da aber noch verständnisfragen:

1) Du benutzt immer noch nicht die [ python]-Tags.
was meinst du damit genau, meinst du damit die schreibweise der python programmierung?
ich versuche alles so umzusetzen wie es in den tutorials steht, allerdings ist deine schreibweise irgendwie kürzer und einfacher :wink:

2) Die globalen Variablen könntest du IMHO auch loswerden und das eher in Funktionen auslagern...
Wie muss ich mir das vorstellen, soll ich es so umsetzen?

Code: Alles auswählen

def end_alt():
	alt = raw_input("Welche Dateiendung, z.B.: 'txt', soll geändert werden? ")
end_alt()
wahrscheinlich nicht, bisher hast du mir immer aufgezeigt wie man alles kürzer schreiben kann und das würds ja eher länger machen...

3)

Code: Alles auswählen

print "\n".join(names)
hab ich umgesetzt, klappt astrein!

4)

Code: Alles auswählen

for x in names:
    os.rename(x, os.path.splitext(x)[0]+'.'+neu)
auch umgesetzt und hat mich am meisten begeistert, ich habe 3 stunden gebraucht bis ich es auf meine weise hinbekommen habe und jetzt sehe ich wie einfach man es wirklich machen kann, genial :mrgreen:

danke und gruß
korkak
Antworten