Чтение рекурсивной папки Python

225

У меня есть фон 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()
Брок Вульф
источник

Ответы:

347

Убедитесь, что вы понимаете три возвращаемых значения 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). Кстати, «файл» является встроенным, поэтому вы обычно не используете его в качестве имени переменной.

Другая проблема - ваши циклы, которые должны быть такими, например:

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()
AndiDog
источник
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)

Как указано в документации

Если рекурсивный имеет значение true, шаблон «**» будет соответствовать любым файлам и нулю или более каталогов и подкаталогов.

Если вы хотите каждый файл, вы можете использовать

import glob

for filename in glob.iglob(root_dir + '**/*', recursive=True):
     print(filename)
ChillarAnand
источник
TypeError: iglob () получил неожиданный аргумент ключевого слова 'recursive'
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
Майки
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
Брок Вульф
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)
chorbs
источник
6

Если вам нужен плоский список всех путей под данным каталогом (как 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, если это файл, то мы можем прочитать этот файл. Здесь я печатаю файл.

Neeraj Sonaniya
источник
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()
ghostdog74
источник
Похоже, этот код работает только для папок 2 уровня (или более). Тем не менее это делает меня ближе.
Брок Вульф
1

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

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
b1r3k
источник
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 = walk, 2 = listdir, 3 = isdir, вместо того, чтобы просто ходить и перебирать «subdir» и «files»).
Люк
0

Я думаю, проблема в том, что вы os.walkнеправильно обрабатываете вывод .

Во-первых, измените:

filePath = rootdir + '/' + file

чтобы:

filePath = root + '/' + file

rootdirВаш фиксированный начальный каталог; rootэто каталог , возвращаемый os.walk.

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

Дэйв Уэбб
источник
У меня есть данные в каждом подкаталоге, поэтому мне нужно иметь отдельный текстовый файл для содержимого каждого каталога.
Брок Вульф
@Brock: часть файлов - это список файлов в текущем каталоге. Так что отступ действительно неправильный. Вы пишете filePath = rootdir + '/' + file, это звучит неправильно: файл находится в списке текущих файлов, значит, вы пишете во множество существующих файлов?
Алок Сингхал