Bestimmten Ordner in C:\-Verzeichnisbaum finden

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.
DMD-OL
User
Beiträge: 315
Registriert: Samstag 26. Dezember 2015, 16:21

hallo
ich möchte gern nach nur nach einem einzigen bestimmten Ordner auf Festplatte C:\ suchen, wobei ich mir dann
gerne die pfadangabe nur bis zum gesuchten ordner ausgeben lassen würde, sobald der ordner einmal gefunden wurde...
soll heißen, wenn ich Python27 suche (egal wo der auf der festplatte ist) hätte ich gern beispielsweise:
C:\Users\Python27 oder
C:\Window\Brechstange\Python27

ich bekomm das nur für ordner direkt auf C:\ hin und auch leider ohne pfadangabe :(
erste versuche bringen mich auf dies:

Code: Alles auswählen

import os

os.chdir(r'\ ')

#rootDir = 'Python27'
rootDir = 'Users'
#rootDir = 'Windows'

counter = 0
for dirName, subdirList, fileList in os.walk(rootDir):
    if counter == 0:
        print dirName
        print subdirList
        print fileList
        counter += 1
    else:
        break
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@DMD-OL: vergiss erst mal, dass es chdir gibt. Arbeite statt dessen mit absoluten Pfaden. Wo suchst Du etwas? Dein rootDir ist ja wohl 'C:\' weil das der Startpunkt Deiner Suche ist. Was bei Dir rootDir heißt sollte also searching_for heißen.
BlackJack

@DMD-OL: Der `counter` macht keinen Sinn weil die Schleife durch diese Logik grundsätzlich gleich beim ersten Durchlauf abgebrochen wird. Da macht also nicht nur `counter` keinen Sinn, sondern auch die Schleife ist überflüssig. Der Code ist im Grunde nahezu äquivalent zu dem hier:

Code: Alles auswählen

import os


dir_name, sub_directories, filenames = next(os.walk('C:/Users'))
print dir_name
print sub_directories
print filenames
Als erstes solltest Du das in eine Funktion verpacken und nicht auf Modulebene schreiben. Dann kann die Schleife durch ein ``return`` abgebrochen werden welches das gewünschte Ergebnis an den Aufrufer zurück gibt.

Dann musst Du die Schleife solange durchlaufen bis tatsächlich die gewünschte Information gefunden wurde. Darauf musst Du *testen*.

Vielleicht hilft es wenn Du einfach mal die Schleife über eine kleinere Verzeichnishierarchie laufen lässt, statt dem kompletten Laufwerk C:, und Dir anschaust an was für Werte die drei Namen in jedem Schleifendurchlauf gebunden werden. Dann sollte klar werden wie der Test aussehen muss.
kodela
User
Beiträge: 185
Registriert: Montag 12. Oktober 2015, 21:24
Wohnort: Landsberg am Lech
Kontaktdaten:

Ich denke, die gewünschte Ordnersuche könnte man so umsetzen:

Code: Alles auswählen

#!/usr/bin/env python35

import os

def seek_Dir(root_dir, seek_dir):
    os.chdir(r'\ ')
    for dirname, subdirlist, filelist in os.walk(root_dir):
        if seek_dir in dirname:
            return dirname, subdirlist, filelist


def main():
    root_dir = 'C:'
    dir_name, subdir_list, file_list = seek_Dir(root_dir, 'VideoLAN\\VLC')
    print(dir_name)
    print(subdir_list)
    print(file_list)


if __name__ == "__main__":
    main()
Könnte man mit seek_Dir() auch den ganzen Computer nach einem Ordner durchsuchen und wenn ja, wie. Unter Windows gibt der Explorer dafür "Computer" als Adresse aus. "Computer" wird aber als Rootverzeichnis nicht akzeptiert.

Ich finde die Dokumentation zu os.chdir() nicht und weiß auch die Bedeutung von "r" vor '\' nicht und kann nur vermuten, dass damit das absolute Rootverzeichnis gemeint ist. Ich schwimme hier also.
Zuletzt geändert von kodela am Mittwoch 6. Januar 2016, 09:40, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@kodela: lösch das "os.chdir" raus. Das macht doch gar keinen Sinn und schadet nur. Deine Suche ist zu simple. Es würde auch ein Verzeichnis mit den Namen "C:\xyVideoLAN\VLCdu" finden.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Die Idee des Codes ist grundsätzlich in Ordnung. Korrekt sähe es allerdings so aus:

Code: Alles auswählen

import os

def get_folders(base_dir, search_name):
    for dir_name, sub_directories, filenames in os.walk(base_dir):
        if search_name in sub_directories:
            return os.path.join(dir_name, search_name)
Dann würde ich auch noch das `return` durch ein `yield` ersetzen um die Funktion universaler zu machen. Damit hast du dann die Chance über alle gefundenen Verzeichnisse zu iterieren oder alternativ einfach mit `next` das erste gefundene Verzeichnis zu holen.
kodela
User
Beiträge: 185
Registriert: Montag 12. Oktober 2015, 21:24
Wohnort: Landsberg am Lech
Kontaktdaten:

@BlackJack: Ja, da habe ich zu kurz gedacht. Mit meinem Code wird der gesuchte Pfad nicht eindeutig gefunden.

@/me: Da die Suche mit meinem Code nicht korrekt war, habe ich den Vorschlag von Dir übernommen. Alles zusammen sieht damit so aus:

Code: Alles auswählen

#!/usr/bin/env python35

import os


def get_folders(base_dir, search_name):
    for dir_name, sub_directories, filenames in os.walk(base_dir):
        if search_name in sub_directories:
            return os.path.join(dir_name)


def main():
    root_dir = 'C:\\'
    seek_dir = 'VLC'
    dir_name = get_folders(root_dir, 'VLC')
    print(dir_name + '\\' + seek_dir + '\\')


if __name__ == "__main__":
    main()

"""
Ausgabe:

C:\Program Files (x86)\VideoLAN\VLC\

Process finished with exit code 0
"""
Allerdings ist es damit nicht möglich, ein Verzeichnis mit einem bestimmten Unterverzeichnis zu suchen. Das muss aber nicht sein, wenn man das Unterverzeichnis sucht und damit den kompletten Pfad bildet. Mit anderen Worten, ich suchte vorher nach "VideoLAN\VLC" und fand "C:\Program Files (x86)\VideoLAN\VLC". Jetzt suche ich nach "VLC" und finde, wenn ich Glück habe, als erstes "C:\Program Files (x86)\VideoLAN" und kann mir daraus auch "C:\Program Files (x86)\VideoLAN\VLC" bilden, den gesuchten Installationsordner für den VLC media player.

Allerdings kann es sein, dass ich als erstes einen ganz anderen Pfad finde, zum Beispiel "C:\Mediaobjekte\VLC". Ich muss also eine weitere Suche anschließen, die nach "vlc.exe" oder sonst einer Datei, die in dem gesuchten Verzeichnis vertreten sein muss. Das wäre zwar bei meinem ersten Code ebenfalls angebracht, aber die Wahrscheinlichkeit, dass es ein Verzeichnis "C:\Program Files (x86)\VideoLAN\VLC" gibt, das nicht das Installationsverzeichnis für den VLC ist, geht gegen Null.

Für eine weitere Suche hast Du die Verwendung von `yield`an Stelle von 'return' vorgeschlagen. Wenn ich es richtig sehe, kann man damit eine rekursive Suche konstruieren. Das ist mir aber bislang noch nicht gelungen. Versucht habe ich den Aufruf über eine Endlosschleife. Kannst Du eine Beschreibung der Generatorfunktion empfehlen? Schön wäre eine deutsche.

Bei meinem letzten nicht funktionierender Versuch sieht der Aufruf so aus:

Code: Alles auswählen

def main():
    root_dir = 'D:\\'
    seek_dir = 'VLC'
    dir_name = get_folders(root_dir, 'VLC')
    while True:
        print(dir_name + '\\' + seek_dir + '\\')
        # hier auf richtiges Verzeichnis prüfen und gegebenenfals abbrechen
        dir_name = next(get_folders)
Natürlich ist in get_folders() das "return" durch "yield" ersetzt.
BlackJack

@kodela: `dir_name` ist kein Verzeichnisname sondern der Generator. *Den* kann man mit `next()` benutzen wenn man das nächste Ergebnis haben möchte, aber wenn man das in einer Schleife macht dann sollte man besser direkt mit ``for`` über die Ergebnisse iterieren statt ``while True:`` und `next()` zu verwenden. Dann müsste man ja auch noch ein ``try``/``except`` für die `StopIteration`-Ausnahme dazu schreiben. Das wäre im Gegensatz zu einer ``for``-Schleife ziemlich umständlich.

Generatorfunktionen haben nicht zwingend mit Rekursion zu tun. Man kann eine rekursive Generatorfunktion schreiben, muss es aber nicht. In diesem Fall wird ja `os.walk()` verwendet, also ist keine Rekursion in der eigenen Funktion nötig.
kodela
User
Beiträge: 185
Registriert: Montag 12. Oktober 2015, 21:24
Wohnort: Landsberg am Lech
Kontaktdaten:

@BlackJack: Wie kann ich dann diese Anzeige bei einem Breakpoint in der Zeile von main() mit dem "print()" verstehen?

Code: Alles auswählen

dir_name = {str} 'C:\\Program Files (x86)\\VideoLAN'
root_dir = {str} 'C:\\'
seek_dir = {str} 'VLC'
PS:
Aha, diese obige Anzeige habe ich nur bei einem "return". Mit "yield" bekomme ich folgende Fehlermeldung:

Code: Alles auswählen

  File "I:/Python/PyCharm/Textmenues/test.py", line 17, in main
    print(dir_name + '\\' + seek_dir + '\\')
TypeError: unsupported operand type(s) for +: 'generator' and 'str'
Da geht mir jetzt ein Licht auf. Danke für Deinen Hinweis.

Und die Anzeige beim Debuggen sieht nun so aus:

Code: Alles auswählen

dir_name = {generator} <generator object get_folders at 0x02EDB900>
root_dir = {str} 'C:\\'
seek_dir = {str} 'VLC'
Wie komme ich jetzt aber zu dem gesuchten Pfad-Bezeichner?
Zuletzt geändert von kodela am Donnerstag 7. Januar 2016, 00:40, insgesamt 1-mal geändert.
BlackJack

@kodela: Gar nicht. Dann hast Du da kein ``yield`` statt ``return`` stehen. Eine Generatorfunktion liefert beim Aufruf ein Generatorobjekt, sonst ist es keine Generatorfunktion.
kodela
User
Beiträge: 185
Registriert: Montag 12. Oktober 2015, 21:24
Wohnort: Landsberg am Lech
Kontaktdaten:

@BlackJack: Doch, das hatte ich schon, deshalb kam ja die Fehlermeldung, weil mit "yield" der Rückgabewert nicht mehr als String behandelt werden konnte. Siehe auch meine PS-Anmerkung.
BlackJack

@kodela: Die Pfade bekommst Du vom Generatorobjekt. Zum Beispiel über eine ``for``-Schleife über das Objekt oder in dem Du `next()` damit aufrufst. Je nach dem ob man über alle iterieren möchte oder eben nur gezielt einen/den ersten Pfad haben möchte.

Die Dokumentation zu Generatorfunktionen bzw. dem ``yield``-Schlüsselwort sollte irgendwo den Link zum entsprechenden PEP enthalten wo das ganze etwas ausführlicher beschrieben wird. Allerdings auf Englisch. „It's english, get over it”.
sepplx123
User
Beiträge: 13
Registriert: Montag 14. Dezember 2015, 04:13

Wenn man nicht nur auf einer Festplatte sondern auf allen suchen möchte, so habe ich folgendes gefunden:
https://pypi.python.org/pypi/psutil
Das könnte auch noch hilfreich sein: http://stackoverflow.com/questions/3596 ... ws-and-mac

Disks
>>> psutil.disk_partitions()
[sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid'),
sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw')]
>>>
>>> psutil.disk_usage('/')
sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5)
>>>
>>> psutil.disk_io_counters(perdisk=False)
sdiskio(read_count=719566, write_count=1082197, read_bytes=18626220032, write_bytes=24081764352, read_time=5023392, write_time=63199568)
>>>

Mal kurz in der shell getestet (bei windows):

Code: Alles auswählen

import psutil
for element in psutil.disk_partitions():
    print element.device

C:\
D:\
E:\
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@kodela: normalerweise sucht man ja nach Programmen nur in den Standardpfaden und nicht auf der ganzen Festplatte. Sollte ein Nutzer keinen Standardpfad verwenden, muß er halt den Pfad irgendwo von Hand angeben. Das erlaubt einem auch, mehrere Versionen auf der Platte zu haben und nach Bedarf explizit eine auszuwählen.

Code: Alles auswählen

import os
 
def ifilter_folders(base_dir, search_name):
    for dir_name, sub_directories, filenames in os.walk(base_dir):
        if search_name in sub_directories:
            yield os.path.join(dir_name, search_name)

def main():
    root_dir = 'D:\\'
    seek_dir = 'VLC'
    for dir_name in ifilter_folders(root_dir, seek_dir):
        if os.path.exists(os.path.join(dir_name, 'vlc.exe'):
            break
    else:
        print("vlc.exe not found")
        return
    print("vlc.exe found in %s" % dir_name)
kodela
User
Beiträge: 185
Registriert: Montag 12. Oktober 2015, 21:24
Wohnort: Landsberg am Lech
Kontaktdaten:

@sepplx123: Danke für Deine Infos für eine Suche über mehrere Laufwerke. Eine solche Suche ist für mich zwar nur von sekundärer Bedeutung. Es ging mir mehr um die grundsätzliche Frage nach dem obersten Punkt der Hierarchie für eine Suche. Das ist, wie ich jetzt weiß, das Rootverzeichnis eines Laufwerks. Ich werde aber trotzdem Deinen Hinweisen einmal nachgehen.

@Sirius3: Du hast mir ja eine (fast) fertige Lösung geliefert, Danke dafür. Zwei kleine Änderungen waren erforderlich. In der Zeile 13 fehlt am Ende eine schließende Klammer und der else-Zweig ab Zeile 14 muss noch um einen Tab eingerückt werden.

Eine Schwachstellen hat diese Lösung noch. Angenommen "C:\Program Files (x86)\VideoLAN\VLC" sei der zu findende Pfad und es gibt den Pfad "C:\VLC", in dem sich keine "vlc.exe" befindet, dann wird die Suche abgebrochen und damit der gesuchte Installationsordner nicht gefunden. Es müsste also weiter gesucht werden.

Hier stehe ich mit meinem Wissen noch vor einer Schranke. Auf Grund der Hinweise von @BlackJack weiß ich nun zwar, dass bei einem Rücksprung über "yield" ein Generatorobjekt zurück gegeben wird, über das man dann die Suche fortsetzen könnte. Das muss ja mit dem Code von Dir doch auch möglich sein, aber ich komme nicht dahinter, wie.

MfG, kodela
BlackJack

@kodela: Das ``else`` muss *nicht* weiter eingerückt werden! Das gehört zur Schleife und nicht zum ``if``. Womit sich auch die Frage nach dem Weitersuchen erledigt, denn die Schleife läuft so lange bis alle passenden Verzeichnisse abgearbeitet sind.
kodela
User
Beiträge: 185
Registriert: Montag 12. Oktober 2015, 21:24
Wohnort: Landsberg am Lech
Kontaktdaten:

@BlackJack: Oh, Danke. Da hatte ich mal wieder einen "Kurzschluss". Ein "for" mit einem else-Zweig, das habe ich noch nicht gesehen.
kodela
User
Beiträge: 185
Registriert: Montag 12. Oktober 2015, 21:24
Wohnort: Landsberg am Lech
Kontaktdaten:

@DMD-OL: Ich habe Dein Thema mitbenützt, erweitert für die Suche nach Ordner mit einer bestimmten Datei. Dabei habe ich Dank der Hilfe von @BlackJack und @Sirius3 einiges dazu gelernt. Damit aber Deine Anfrage auch Beachtung findet, hier eine Lösung für die reine Ordnersuche, ausgedehnt auf jedes Vorkommen des zu suchenden Ordner im angegebenen Rootpfad. Hier das Ergebnis, das weitgehend dem Vorschlag von @Sirius3 entspricht:

Code: Alles auswählen

#!/usr/bin/env python35
# coding=utf-8

import os


def ifilter_folders(base_dir, search_name):
    for dir_name, sub_directories, filenames in os.walk(base_dir):
        if search_name in sub_directories:
            yield os.path.join(dir_name, search_name)


def main():
    root_dir = 'C:\\'
    seek_dir = 'temp'
    dir_name = ''
    gefunden = False
    for dir_name in ifilter_folders(root_dir, seek_dir):
        if not gefunden:
            print("Anzeige aller Pfade mit einem %s-Ordner:" % seek_dir)
        print(dir_name)
        gefunden = True
    else:
        # bezieht sich auf den for- und nicht auf den if-Block
        if not gefunden:
            print("Ein Ordner \'%s\' wurde nicht gefunden!" % seek_dir)


if __name__ == "__main__":
    main()
Etwas möchte ich noch erwähnen. Ich habe diesen Code unter den Versionen 2.7.11 und 3.5 getestet. Dabei stellte ich fest, dass das Skript unter der Version 3.5 um den Faktor 5 schneller ausgeführt wurde (15 Sek. mit 2.7.11 und 3 Sek. mit 3.5). Wie erklärt sich das?

Gruß, kodela
Zuletzt geändert von kodela am Freitag 8. Januar 2016, 00:29, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@kodela: ein else bei for ist unsinnig, wenn es im for kein break gibt. Wenn os.walk ein Verzeichnis findet, dann ist es mit großer Wahrscheinlichkeit auch bei os.path.exists noch da, die if-Abfrage ist also überflüssig; genauso ein os.path.join mit nur einem Argument. Das Escapen der einfachen Anführungszeichen ist unnötig und erschwert nur das Lesen.
kodela
User
Beiträge: 185
Registriert: Montag 12. Oktober 2015, 21:24
Wohnort: Landsberg am Lech
Kontaktdaten:

@Sirius3: Danke für Deine Hinweise. Ja, es ist richtig, dass das else am Ende der for-Schleife unsinnig ist. Der Sichtbereich für gefunden beschränkt sich ja nicht nur auf die for-Schleife.

Auch Dein zweiter Einwand ist richtig. Das ist mir auch selbst aufgefallen. Für die Rückgabe von def ifilter_folders() reicht ein yield dir_name aus. Ich wollte dies eben berichtigen, als dann aber Deine Antwort schon da war.
Antworten