2016-11-02 4 views
3

Благодаря большой и запутанной структуру каталогов, мой скрипт поиск слишком много каталогов:Таргетинга каталога с os.walk

root-- 
    | 
    --Project A-- 
        | 
        -- Irrelevant 
        -- Irrelevant 
        -- TARGET 
    | 
    --Project B-- 
        | 
        -- Irrelevant 
        -- TARGET 
        -- Irrelevant 
    | 
    -- Irrelevant -- 
         | 
         --- Irrelevant 

Каталог TARGET является единственным, кто мне нужно, чтобы пройти и непротиворечивый имя в каждом проекте (мы просто назовем его Target здесь).

Я посмотрел на этот вопрос:

Excluding directories in os.walk

, но вместо того, чтобы исключить, я должен включать «целевой» каталог, который не находится на уровне «корень», но на один уровень вниз.

Я пытался что-то тому подобное:

def walker(path): 
    for dirpath, dirnames, filenames in os.walk(path): 
     dirnames[:] = set(['TARGET']) 

Но это один эффект корневой каталог (тем самым игнорируя все каталоги, необходимые для прохождения, Project A, B Project ...)

+3

Попробуйте: 'if 'TARGET' в dirnames: dirnames [:] = ['TARGET']' ... – Bakuriu

+0

как с помощью 'glob.glob' сначала получить список подцелей« target », а затем использовать' os .listdir' или 'os.walk' по результатам? –

+0

@CAB Это означает, что 'walk' не пройдет мимо корневого каталога. –

ответ

3

Вопрос с вашим кодом является то, что вы всегда изменениями dirnames списка, но это означает, что даже на корневом уровне всех подкаталоги удаляются, и, следовательно, рекурсивные вызовы не в конечном итоге посещение различных Project X каталогов.

Что вы хотите, чтобы очистить другие каталоги только когда TARGET один присутствует:

if 'TARGET' in dirnames: 
    dirnames[:] = ['TARGET'] 

Это позволит os.walk вызов посетить Project X каталоги, но не позволит ему идти внутри Irrelevant из них.

+0

Похоже, это ничего не сделало бы, если исходный путь '/ root', потому что он не содержит подкаталога с именем' TARGET'. – martineau

+1

@martineau Да, это правильно. По * не * удаляя имена каталогов, он * делает * рекурсивным в другие каталоги, что является точкой ответа. – Bakuriu

+0

Ах, прямо ... теперь я понял. Хороший ответ. – martineau

2

Для сценария с подобным списком я бы предложил использовать glob.iglob, чтобы получить каталоги по шаблону. Это генератор, поэтому вы получите каждый результат так быстро, как он их найдет (Примечание: на момент написания он по-прежнему реализован с os.listdir под капотом, а не os.scandir, так что это всего лишь половина генератора, каждый каталог сканируется с нетерпением, но он проверяет только следующую директорию после завершения ввода значений из текущего каталога). Например, в данном случае:

from future_builtins import filter # Only on Py2 to get generator based filter 

import os.path 
import glob 

from operator import methodcaller 

try: 
    from os import scandir  # Built-in on 3.5 and above 
except ImportError: 
    from scandir import scandir # PyPI package on 3.4 and below 

# If on 3.4+, use glob.escape for safety; before then, if path might contain glob 
# special characters and you don't want them processed you need to escape manually 
globpat = os.path.join(glob.escape(path), '*', 'TARGET') 

# Find paths matching the pattern, filtering out non-directories as we go: 
for targetdir in filter(os.path.isdir, glob.iglob(globpat)): 
    # targetdir is the qualified name of a single directory matching the pattern, 
    # so if you want to process the files in that directory, you can follow up with: 
    for fileentry in filter(methodcaller('is_file'), scandir(targetdir)): 
     # fileentry is a DirEntry with attributes for .name, .path, etc. 

Смотрите документацию по os.scandir для более продвинутого использования, или вы можете просто сделать внутренний цикл вызова os.walk, чтобы сохранить большую часть исходного кода, как есть.

Если вы действительно должны использовать os.walk, то вы можете быть в большей степени ориентированным на то, как вы обрезаете dirs. Поскольку вы указали, что все каталоги TARGET должны быть только на один уровень вниз, это на самом деле довольно легко. os.walk по умолчанию заканчивается сверху вниз, что означает, что первый набор результатов будет корневым каталогом (который вы не хотите обрезать только для TARGET записей). Так что вы можете сделать:

import fnmatch 

for i, (dirpath, dirs, files) in enumerate(os.walk(path)): 
    if i == 0: 
     # Top level dir, prune non-Project dirs 
     dirs[:] = fnmatch.filter(dirs, 'Project *') 
    elif os.path.samefile(os.path.dirname(dirpath), path): 
     # Second level dir, prune non-TARGET dirs 
     dirs[:] = fnmatch.filter(dirs, 'TARGET') 
    else: 
     # Do whatever handling you'd normally do for files and directories 
     # located under path/Project */TARGET/ 
+0

Я ошибаюсь или это не удается, если каталоги могут быть вложены более одного уровня? И если это не терпит неудачу, это означает, что он все равно будет отображать содержимое каталогов 'Irrelevant', и для каждого пути внутри них он будет проверять, что он не проверяет шаблон, но все равно ему придется потратить впустую это время , – Bakuriu

+0

@Bakuriu: Если они могут быть вложены более чем на один уровень, сделайте 'globpat = os.path.join (glob.escape (путь), '**', 'TARGET')' и сделайте вызов 'iglob'' glob .iglob (globpat, recursive = True) ', и он будет спускаться бесконечно. Кажется, что ФП хочет, чтобы он был ровно на один уровень вниз, поэтому глубокая рекурсия не нужна. Вы также можете настроить подстановочные знаки, чтобы избежать «неуправляемых» каталогов для этого случая с одним уровнем, например. заменив компонент '' * ''на« Project [AB] »для очень целевого выбора или« Project * »для любого каталога с префиксом« Project ». – ShadowRanger

+0

Да, но я хочу сказать, что если вы используете '**', ваш ответ становится неэффективным. 'iglob' не может знать, что * ничего * ниже' Irrelevant' имеет значение, но все равно придется проверять все содержимое (что, если 'Irrelevant' содержит миллионы файлов или вложенных подкаталогов, потребуется значительное время). – Bakuriu

Смежные вопросы