Seite 1 von 1
untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Montag 20. März 2017, 18:43
von Holger Chapman
Hallo,
ich schreibe gerade ein kleines Programm, das für alle angegebenen Verzeichnisse (inkl. Unterverzeichnisse usw.) bestimmte Aktionen durchführt.
Jetzt will ich aus der Liste der Verzeichnisse, die das Programm durchgeht, diejenigen aussortieren, die schon in einem anderen Verzeichnis enthalten sind.
Also: aus
soll werden:
. ('/d1/sd1' und '/d1/sd1/file1' sind beide in '/d1' enthalten und können daher aussortiert werden.)
Ich bastele jetzt schon 'ne ganze Weile mit for-Schleifen herum ... (
Code: Alles auswählen
if len(liste) > 1:
neue_liste.append(liste[0])
for i in range(len(liste) - 1):
i_ist_neu = True
for j in range(i + 1, len(liste)):
print
print "compare " + liste[i] + " and " + liste[j]
print "compare '" + liste[i][:len(liste[j])] + "' and '" + liste[j][:len(liste[j])] + "'"
if liste[i][:len(liste[j])] == liste[j][:len(liste[j])]:
) ... aber irgendwie habe ich das Gefühl, das mein ganzer Ansatz falsch ist und es eine viel elegantere Möglichkeit geben muss. - Vermutlich irgendwas mit list comprehensions.
Kann jemand von euch mir einen Tipp geben?
Vielen Dank!
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Montag 20. März 2017, 19:48
von noisefloor
Hallo,
basierend auf dem Beispiel wäre das einfacher:
[codebox=pycon file=Unbenannt.txt]>>> my_list = ['/d1/sd1', '/d1/sd1/file1', '/d2', '/d1']
>>> result =[x for x in my_list if x.count('/')==1]
>>> result
['/d2', '/d1']
>>> [/code]
Wie gesagt, basierend auf dem Beispiel von dir.
Wenn die Ausgangsliste z.B. ` ['/d1/sd1', '/d1/sd1/file1', '/d2', '/d1', '/d3/foo']` wäre, dann würde dann das so nicht funktionieren.
Die andere Frage wäre, ob du die Ausgangsliste nicht schon so generieren kannst, dass Unterverzeichnisse nicht enthalten sind.
Gruß, noisefloor
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Montag 20. März 2017, 20:04
von noisefloor
Hallo,
Nachtrag: dem 2. Fall kannst du mit einem regulären Ausdruck und einer Set-Comprehension zu Leibe rücken:
[codebox=pycon file=Unbenannt.txt]>>> import re
>>> my_list2 = ['/d1/sd1', '/d1/sd1/file1', '/d2', '/d1', '/d3/foo']
>>> pattern = r'^(\/\w+).*$'
>>> compiled = re.compile(pattern)
>>> result2 = list({compiled.match(x).group(1) for x in my_list2})
>>> result2
['/d2', '/d3', '/d1']
>>>[/code]
Gruß, noisefloor
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Montag 20. März 2017, 21:19
von Holger Chapman
Hallo noisefloor,
vielen Dank für Deine Antworten! - Den zweiten Code mit der Regex habe ich noch nicht ganz verstanden, schaue ich mir aber nochmal in Ruhe an.
Leider tut der Code nicht ganz das, was ich will. Für ['/d1/sd1', '/d1/sd1/file1', '/d2', '/d1', '/d3/foo'] würde ich als Ergebnis erwarten:
['/d2', '/d1', '/d3/foo']. (Für '/d3/foo' gibt es kein übergeordnetes Verzeichnis in der Liste.)
Schönen Gruß
Holger
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Dienstag 21. März 2017, 07:37
von noisefloor
Hallo,
stimmt, Denkfehler meinerseits. So bekommst du alle gesuchten Verzeichnisse:
[codebox=pycon file=Unbenannt.txt]>>> import re
>>> my_list2 = ['/d1/sd1', '/d1/sd1/file1', '/d2', '/d1', '/d3/foo']
>>> pattern = r'^(\/\w+).*$'
>>> compiled = re.compile(pattern)
>>> top_level_dirs = [x for x in my_list2 if x.count('/')==1]
>>> result = [x for x in my_list2 if compiled.match(x).group(1) not in top_level_dirs]
>>> result
['/d3/foo']
>>> result + top_level_dirs
['/d3/foo', '/d2', '/d1'][/code]
Die Regex sucht einfach nur nach einen Ausdruck, der mit dem Slash beginnt, gefolgt von beliebig vielen Buchstaben und Zahlen, gefolgt von beliebigen Zeichen. Der Slash plus Zahlen- / Zeichenfolge ist die 1. Capture-Group und die enthält dann den ersten Teil der Verzeichnisses.
Gruß, noisefloor
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Dienstag 21. März 2017, 08:43
von Sirius3
@noisefloor: mit regulären Ausdrücken auf Verzeichnisse einzuprügeln ist der falsche Weg. Dafür gibt es Funktionen in os.path.
Code: Alles auswählen
directories = ['/d1', '/d1/sd1', '/d1/sd1/file1', '/d2', '/d3/foo']
directories.sort()
base_directories = []
for directory in directories:
if not any(os.path.commonprefix([d, directory]) == d for d in base_directories):
base_directories.append(directory)
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Dienstag 21. März 2017, 17:46
von Holger Chapman
@Sirius3:
Danke, das macht genau das, was ich gesucht habe! "os.path.commonprefix" kannte ich noch nicht ...
Schönen Gruß
Holger
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Dienstag 21. März 2017, 20:04
von noisefloor
Hallo,
`commonprefix` aus `os.path` kannte ich auch bis dato nicht.
Aber: es ist IMHO keine universelle Lösung für das gegebene Problem:
Code: Alles auswählen
import os.path
directories = ['/d1', '/d1/sd1', '/d1/sd1/file1', '/d2', '/d3/foo', '/d3/fool']
directories.sort()
base_directories = []
for directory in directories:
if not any(os.path.commonprefix([d, directory]) == d for d in base_directories):
base_directories.append(directory)
print(base_directories)
liefert
korrekt wäre `['/d1', '/d2', '/d3/foo', '/d3/fool']
BTW: ich verstehe den Sinn des `directories.sort()` an der Stelle nicht...
Gruß, noisefloor
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Donnerstag 23. März 2017, 22:13
von Holger Chapman
Hallo!
Danke für den Hinweis, noisefloor. Ich hatte den Code von Sirius3 schon eingebaut, aber der funktioniert tatsächlich noch nicht richtig.
Mit den Anregungen von euch beiden, noisefloor und Sirius3, habe ich jetzt Code geschrieben, der in meinen Tests immer gemacht hat, was ich will. - Vielleicht findet ihr ja doch noch Ausnahmesituationen, die ich nicht berücksichtigt habe, oder Dinge, die ich eleganter programmieren kann?
Hier jedenfalls der funktionierende Code:
Code: Alles auswählen
#!/usr/bin/env python2
import os
def ohne_dateien_aus_unterordnern(liste):
liste = [os.path.abspath(i) for i in liste] # rel. Dateinamen -> absolut
liste.sort() # damit uebergeordnete Ordner vor Unter-Elementen stehen
if len(liste) > 1:
tmp_liste = []
for i in liste:
is_new = True
if len(tmp_liste) > 0:
for j in tmp_liste:
if os.path.commonprefix([i + os.sep, j + os.sep]) in tmp_liste:
is_new = False
if is_new == True:
tmp_liste.append(i + os.sep)
tmp_liste = [i[:-1] for i in tmp_liste]
return tmp_liste
else:
return liste
# main
liste = ['/d1/sd1', '/d1', '/d1/sd1/file1', '/d2', '/d3/foo', '/d3/foo2']
print ' vorher: ' + str(liste)
liste = ohne_dateien_aus_unterordnern(liste)
print 'nachher: ' + str(liste)
Das Ergebnis sieht so aus:
Code: Alles auswählen
$ ./parms.py
vorher: ['/d1/sd1', '/d1', '/d1/sd1/file1', '/d2', '/d3/foo', '/d3/foo2']
nachher: ['/d1', '/d2', '/d3/foo', '/d3/foo2']
Viele Grüße!
Holger
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Freitag 24. März 2017, 07:24
von noisefloor
Hallo,
was man auf jeden Fall kürzer / eleganter schreiben kann:
`if tmp_liste:` statt `if len(tmp_liste) > 0:` und `if is_new:` statt`if is_new == True:`
Gruß, noisefloor
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Freitag 24. März 2017, 09:13
von Sirius3
@Holger Chapman: Variabelnnamen sollten aussagekräftig sein. liste und tmp_liste sind zu generisch, i, j sind total falsch, weil man da eine Zahl erwartet. Normalerweise versucht man Flags zu vermeiden. Aus
Code: Alles auswählen
is_new = True
if len(tmp_liste) > 0:
for j in tmp_liste:
if os.path.commonprefix([i + os.sep, j + os.sep]) in tmp_liste:
is_new = False
if is_new == True:
tmp_liste.append(i + os.sep)
wird
Code: Alles auswählen
for j in tmp_liste:
if os.path.commonprefix([i + os.sep, j + os.sep]) in tmp_liste:
break
else:
tmp_liste.append(i + os.sep)
Die if-Abfrage ist unnötig, weil bei einer leeren Liste die Schleife einfach 0-mal durchlaufen wird. Der else-Zweig bei for wird genau dann durchlaufen, wenn die Schleife nicht durch break verlassen wurde, also genau dann, wenn is_new nicht auf False gesetzt wurde.
Noch ein bißchen vereinfacht, sieht das dann so aus:
Code: Alles auswählen
def is_subdirectory(parent, path):
return path.startswith(parent + os.sep)
def filter_common_paths(paths):
paths = sorted(os.path.realpath(os.path.normpath(path)) for path in paths)
result = []
for path in paths:
if not result or not is_subdirectory(result[-1], path):
result.append(path)
return result
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Samstag 25. März 2017, 23:58
von Holger Chapman
Hallo Sirius3,
vielen Dank, Dein Vorschlag hat den Code noch ein ganzes Stück verbessert.
Was ich nicht verstanden habe: Ist es von Vorteil, die Funktion "is_subdirectory" zu benutzen? Wäre es von Nachteil, an Stelle von
Code: Alles auswählen
def is_subdirectory(parent, path):
return path.startswith(parent + os.sep)
if not result or not is_subdirectory(result[-1], path):
direkt die Bedingung
Code: Alles auswählen
if not result or not path.startswith(result[-1] + os.sep):
zu benutzen und die Funktion "is_subdirectory" wegzulassen? - Das erscheint mir übersichtlicher, als eine Funktion zu definieren, die nur an einer Stelle verwendet wird.
Und was ich anders machen möchte: Aus Deinem
Code: Alles auswählen
paths = sorted(os.path.realpath(os.path.normpath(path)) for path in paths)
möchte ich
Code: Alles auswählen
paths = sorted(os.path.abspath(path) for path in paths)
machen, weil symbolische Links in meinem Anwendungsfall nicht aufgelöst werden sollen.
Außerdem verwende ich bei den Variablenbezeichnungen lieber "file" als "dir" oder "path", weil der Code unabhängig davon funktionieren sollte, ob die Liste Dateien, Verzeichnisse oder Symlinks (oder eine Kombination davon) enthält.
(Wie nennt man eigentlich ein Element, das in einem Verzeichnis eines Dateisystems gelistet wird, wenn man noch nicht weiß, was genau es ist ("echte" Datei, Unterverzeichnis, Symlink, ...)? - "fso" (file system object)? "directory_entry"? Oder einfach nur "file" (gemäß dem Unix-Prinzip "Everything is a file")? - Ich habe mich für letzteres entschieden.)
Jedenfalls: So sieht mein kleines Programm jetzt aus:
Code: Alles auswählen
#!/usr/bin/env python2
import os
def simplify_file_list(file_list):
# - remove elements contained in higher-level directories, if element + dir are in file_list
# - convert relative file names to absolute file names (don't follow symlinks)
# - sort elements alphabetically
file_list = sorted(os.path.abspath(file_name) for file_name in file_list)
simplified_file_list = []
for file_name in file_list:
if not simplified_file_list or not file_name.startswith(simplified_file_list[-1] + os.sep):
simplified_file_list.append(file_name)
return simplified_file_list
# main
file_list = ['/d3/foo2', '/d1/sd1', '/d1', '/d1/sd1/file1', '/d2', '/d3/foo']
print 'before: ' + str(file_list)
file_list = simplify_file_list(file_list)
print ' after: ' + str(file_list)
Wie gesagt, vielen Dank für Deine Hilfe! - Aus nicht funktionierendem Code ist erst funktionierender Code und dann schlanker und übersichtlicher funktionierender Code geworden. - Du hast mir sehr geholfen!
Viele Grüße
Holger
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Sonntag 26. März 2017, 09:06
von Sirius3
@Holger Chapman: Funktionsnamen helfen auch, den Code zu verstehen, gerade bei kryptischen Stringoparationen, wo man nicht sofort sieht, was der Grund dafür ist.
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Sonntag 26. März 2017, 09:41
von nezzcarth
Holger Chapman hat geschrieben:
(Wie nennt man eigentlich ein Element, das in einem Verzeichnis eines Dateisystems gelistet wird, wenn man noch nicht weiß, was genau es ist ("echte" Datei, Unterverzeichnis, Symlink, ...)? - "fso" (file system object)? "directory_entry"? Oder einfach nur "file" (gemäß dem Unix-Prinzip "Everything is a file")? - Ich habe mich für letzteres entschieden.)
Ich würde auch bei 'file' bleiben. 'File system object' klingt nach den Einheiten, mit denen auf Dateisystemebene operiert wird (also Inodes unter Unix-Systemen); das ist aber hier eher nicht gemeint.
Re: untergeordnete Dateien/Verzeichnisse aussortieren
Verfasst: Sonntag 26. März 2017, 11:15
von BlackJack
Wobei `file` a) der Name des Dateityps in Python ist, und b) hier gar keine Dateien sondern Dateinamen gemeint sind, was semantisch einen grossen Unterschied macht. Und wenn es nicht nur um Datei*namen* geht, sondern auch Pfade dranhängen ist `path` IMHO der passendere Name für so etwas. Wenn es nur der Pfad ist also der Pfad zu einem Verzeichnis, dann `dir_name` oder `folder_name`.