Прочитана рекурсивна папка Python


224

У мене є C ++ / Obj-C, і я просто відкриваю Python (пишу його близько години). Я пишу сценарій для рекурсивного зчитування вмісту текстових файлів у структурі папок.

Проблема, яку я маю, - написаний нами код, працюватиме лише для однієї папки. Я можу зрозуміти, чому в коді (див. #hardcoded path) Я просто не знаю, як я можу рухатись вперед з Python, оскільки мій досвід роботи з ним лише новий.

Код Python:

import os
import sys

rootdir = sys.argv[1]

for root, subFolders, files in os.walk(rootdir):

    for folder in subFolders:
        outfileName = rootdir + "/" + folder + "/py-outfile.txt" # hardcoded path
        folderOut = open( outfileName, 'w' )
        print "outfileName is " + outfileName

        for file in files:
            filePath = rootdir + '/' + file
            f = open( filePath, 'r' )
            toWrite = f.read()
            print "Writing '" + toWrite + "' to" + filePath
            folderOut.write( toWrite )
            f.close()

        folderOut.close()

Відповіді:


346

Переконайтеся, що ви розумієте три повернені значення os.walk:

for root, subdirs, files in os.walk(rootdir):

має таке значення:

  • root: Поточний шлях, який "пройшов"
  • subdirs: Файли в rootкаталозі типу
  • files: Файли типу root(не в subdirs) іншого типу, крім каталогу

І будь ласка, використовуйте os.path.joinзамість того, щоб поєднати з косою рисою! Ваша проблема полягає в тому, що filePath = rootdir + '/' + fileви повинні об'єднати поточну "прогулянку" папку замість верхньої папки. Так і повинно бути filePath = os.path.join(root, file). BTW "файл" є вбудованим, тому його зазвичай не використовують як ім'я змінної.

Ще одна проблема - це ваші петлі, які мають бути таким, наприклад:

import os
import sys

walk_dir = sys.argv[1]

print('walk_dir = ' + walk_dir)

# If your current working directory may change during script execution, it's recommended to
# immediately convert program arguments to an absolute path. Then the variable root below will
# be an absolute path as well. Example:
# walk_dir = os.path.abspath(walk_dir)
print('walk_dir (absolute) = ' + os.path.abspath(walk_dir))

for root, subdirs, files in os.walk(walk_dir):
    print('--\nroot = ' + root)
    list_file_path = os.path.join(root, 'my-directory-list.txt')
    print('list_file_path = ' + list_file_path)

    with open(list_file_path, 'wb') as list_file:
        for subdir in subdirs:
            print('\t- subdirectory ' + subdir)

        for filename in files:
            file_path = os.path.join(root, filename)

            print('\t- file %s (full path: %s)' % (filename, file_path))

            with open(file_path, 'rb') as f:
                f_content = f.read()
                list_file.write(('The file %s contains:\n' % filename).encode('utf-8'))
                list_file.write(f_content)
                list_file.write(b'\n')

Якщо ви не знали, withзаява для файлів - це скорочення:

with open('filename', 'rb') as f:
    dosomething()

# is effectively the same as

f = open('filename', 'rb')
try:
    dosomething()
finally:
    f.close()

4
Чудово, безліч відбитків, щоб зрозуміти, що відбувається, і це прекрасно працює. Дякую! +1
Брок Вульф

16
Голова до тих, хто німий / незрозумілий, як я ... цей зразок коду пише файл txt у кожен каталог. Радий, що я перевірив його в папці, що контролюється версією, хоча все, що потрібно для написання сценарію очищення, є і тут :)
Steazy

той другий (найдовший) фрагмент коду працював дуже добре, врятував мене багато нудної роботи
амфібій

1
Оскільки швидкість, якщо, очевидно, найважливіший аспект, os.walkнепогана, хоча я придумав ще швидший шлях os.scandir. Усі globрішення набагато повільніше, ніж walk& scandir. Моя функція, а також повний аналіз швидкості, можна знайти тут: stackoverflow.com/a/59803793/2441026
user136036

112

Якщо ви використовуєте Python 3.5 або вище, ви можете зробити це в 1 рядок.

import glob

for filename in glob.iglob(root_dir + '**/*.txt', recursive=True):
     print(filename)

Як зазначено в документації

Якщо рекурсивна істина, шаблон "**" буде відповідати будь-яким файлам і нульовим або більше каталогів і підкаталогів.

Якщо ви хочете кожен файл, ви можете використовувати

import glob

for filename in glob.iglob(root_dir + '**/*', recursive=True):
     print(filename)

TypeError: iglob () отримав несподіваний аргумент ключового слова 'рекурсивний'
Jewenile

1
Як згадувалося на початку, це лише для Python 3.5+
ChillarAnand

9
root_dir повинен мати кінцеву косу рису (інакше ви отримаєте щось на зразок "папки ** / *" замість "папки / ** / *" в якості першого аргументу). Ви можете використовувати os.path.join (root_dir, ' * / '), але я не знаю, чи прийнятно використовувати os.path.join з магістральними шляхами (хоча це працює і для мого додатка).
drojf

@ChillarAnand Чи можете ви, будь ласка, додати коментар до коду у цій відповіді, на який root_dirпотрібен проділ? Це заощадить час людям (або, принаймні, це заощадило б мені час). Дякую.
Дан

1
Якщо я запускав це, як у відповіді, це не працювало рекурсивно. Для того, щоб зробити цю роботу рекурсивно , мені довелося змінити його на: glob.iglob(root_dir + '**/**', recursive=True). Я працюю в Python 3.8.2
mikey

38

Погодьтесь з Дейвом Веббом, os.walkвийде елемент для кожної директорії в дереві. Справа в тому, що вам просто не потрібно дбати subFolders.

Цей код повинен працювати:

import os
import sys

rootdir = sys.argv[1]

for folder, subs, files in os.walk(rootdir):
    with open(os.path.join(folder, 'python-outfile.txt'), 'w') as dest:
        for filename in files:
            with open(os.path.join(folder, filename), 'r') as src:
                dest.write(src.read())

3
Хороший. Це також працює. Однак я віддаю перевагу версії AndiDog, хоча її довше, тому що зрозуміліше як початківця Python зрозуміліше. +1
Brock Woolf

20

TL; DR: це еквівалент перегляду find -type fвсіх файлів у всіх папках нижче, включаючи поточний:

for currentpath, folders, files in os.walk('.'):
    for file in files:
        print(os.path.join(currentpath, file))

Як вже було сказано в інших відповідях, os.walk()це відповідь, але це можна було б пояснити краще. Це досить просто! Давайте пройдемося по цьому дереву:

docs/
└── doc1.odt
pics/
todo.txt

З цим кодом:

for currentpath, folders, files in os.walk('.'):
    print(currentpath)

Це currentpathпоточна папка, яку він переглядає. Це виведе:

.
./docs
./pics

Отже, це тричі циклічно, бо є три папки: поточна docs, і pics. У кожному циклі він заповнює змінні foldersта filesвсі папки та файли. Покажемо їм:

for currentpath, folders, files in os.walk('.'):
    print(currentpath, folders, files)

Це нам показує:

# currentpath  folders           files
.              ['pics', 'docs']  ['todo.txt']
./pics         []                []
./docs         []                ['doc1.odt']

Таким чином , в першому рядку, ми бачимо , що ми знаходимося в папці ., що вона містить дві папки , а саме picsта docs, і що є один файл, а саме todo.txt. Вам не потрібно нічого робити, щоб повторно вписатись у ці папки, тому що, як бачите, вона автоматично повторюється і просто надає файли в будь-яких папках. І будь-які підпапки цього (хоча таких у прикладі у нас немає).

Якщо ви просто хочете переглядати всі файли, еквівалентні find -type f, ви можете зробити це:

for currentpath, folders, files in os.walk('.'):
    for file in files:
        print(os.path.join(currentpath, file))

Це виводи:

./todo.txt
./docs/doc1.odt

9

pathlibБібліотека дійсно відмінно підходить для роботи з файлами. Ви можете зробити рекурсивний глобус на Pathоб'єкті так.

from pathlib import Path

for elem in Path('/path/to/my/files').rglob('*.*'):
    print(elem)

6

Якщо ви хочете отримати рівний список усіх шляхів під заданим dir (як find .у оболонці):

   files = [ 
       os.path.join(parent, name)
       for (parent, subdirs, files) in os.walk(YOUR_DIRECTORY)
       for name in files + subdirs
   ]

Щоб включити лише повні шляхи до файлів під базовим режимом, не залишайте + subdirs.


6
import glob
import os

root_dir = <root_dir_here>

for filename in glob.iglob(root_dir + '**/**', recursive=True):
    if os.path.isfile(filename):
        with open(filename,'r') as file:
            print(file.read())

**/**використовується для отримання всіх файлів рекурсивно, включаючи directory.

if os.path.isfile(filename)використовується для перевірки, чи filenameє змінною fileабо directory, якщо це файл, то ми можемо прочитати цей файл. Ось я друкую файл.


6

Я знайшов таке, що є найпростішим

from glob import glob
import os

files = [f for f in glob('rootdir/**', recursive=True) if os.path.isfile(f)]

Використання glob('some/path/**', recursive=True)отримує всі файли, але також включає імена каталогів. Додавання if os.path.isfile(f)умови фільтрує цей список лише до існуючих файлів


3

використовувати os.path.join()для побудови своїх шляхів - Це акуратніше:

import os
import sys
rootdir = sys.argv[1]
for root, subFolders, files in os.walk(rootdir):
    for folder in subFolders:
        outfileName = os.path.join(root,folder,"py-outfile.txt")
        folderOut = open( outfileName, 'w' )
        print "outfileName is " + outfileName
        for file in files:
            filePath = os.path.join(root,file)
            toWrite = open( filePath).read()
            print "Writing '" + toWrite + "' to" + filePath
            folderOut.write( toWrite )
        folderOut.close()

Схоже, цей код працює лише для папок 2 рівня (або глибших). Все-таки це мене зближує.
Брок Вульф

1

os.walkрекурсивна хода за замовчуванням. Для кожного dir, починаючи з кореня, він отримує 3-х кортеж (dirpath, dirnames, filename)

from os import walk
from os.path import splitext, join

def select_files(root, files):
    """
    simple logic here to filter out interesting files
    .py files in this example
    """

    selected_files = []

    for file in files:
        #do concatenation here to get full path 
        full_path = join(root, file)
        ext = splitext(file)[1]

        if ext == ".py":
            selected_files.append(full_path)

    return selected_files

def build_recursive_dir_tree(path):
    """
    path    -    where to begin folder scan
    """
    selected_files = []

    for root, dirs, files in walk(path):
        selected_files += select_files(root, files)

    return selected_files

1
У Python 2.6 walk() роблять повернення рекурсивного списку. Я спробував ваш код і отримав список з багатьма повторами ... Якщо ви просто видаліть рядки під коментарем "# рекурсивні дзвінки в папках" - це добре працює
borisbn

1

Спробуйте це:

import os
import sys

for root, subdirs, files in os.walk(path):

    for file in os.listdir(root):

        filePath = os.path.join(root, file)

        if os.path.isdir(filePath):
            pass

        else:
            f = open (filePath, 'r')
            # Do Stuff

Чому б ви робили інший listdir (), а потім isdir (), коли у вас вже є каталог, розділений на файли та каталоги з walk ()? Це виглядає так, що це буде досить повільно у великих деревах (зробіть три систематичні виклики замість одного: 1 = прогулянка, 2 = listdir, 3 = isdir, замість того, щоб просто пройти та пройти через "підкаталоги" та "файли").
Люк

0

Я думаю, що проблема полягає в тому, що ви неправильно обробляєте результат os.walk.

По-перше, зміни:

filePath = rootdir + '/' + file

до:

filePath = root + '/' + file

rootdir- ваш фіксований стартовий каталог; rootце каталог, повернутий користувачем os.walk.

По-друге, вам не потрібно відступати в циклі обробки файлів, оскільки немає сенсу запускати це для кожного підкаталогу. Ви rootвстановите кожен підкаталог. Вам не потрібно обробляти підкаталоги вручну, якщо ви не хочете щось робити з самими каталогами.


У мене є дані в кожному підкаталозі, тому мені потрібно мати окремий текстовий файл для вмісту кожного каталогу.
Брок Вульф

@Brock: частина файлів - це список файлів у поточному каталозі. Тож відступ дійсно неправильний. Ви пишете filePath = rootdir + '/' + file, що не звучить правильно: файл є зі списку поточних файлів, тож ви записуєте до багатьох існуючих файлів?
Алок Сінгал
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.