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()

Answers:


346

确保您了解以下三个返回值os.walk

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

具有以下含义:

  • root:“经过”的当前路径
  • subdirsroot目录类型中的文件
  • 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()

4
精湛的印刷品,可以理解正在发生的一切,并且效果很好。谢谢!+1
布罗克·伍尔夫

16
跟任何人一样愚蠢/无知...这个代码示例将txt文件写入每个目录。很高兴我在版本控制的文件夹中对其进行了测试,尽管我也需要编写清理脚本::
Steazy 2014年

第二个(最长的)代码片段效果很好,为我节省了很多无聊的工作
两栖游戏'18

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)

TypeError:iglob()获得了意外的关键字参数“递归”
Jewenile

1
如开头所述,它仅适用于Python 3.5+
ChillarAnand

9
root_dir必须带有斜杠(否则,您将获得类似于“ folder ** / *”的内容,而不是“ folder / ** / *”作为第一个参数)。您可以使用os.path.join(root_dir,' * / '),但我不知道将os.path.join与通配符路径一起使用是否可以接受(尽管它适用于我的应用程序)。
drojf

@ChillarAnand您能否在此答案中的代码后加一个root_dir斜杠来添加注释?这样可以节省人们的时间(或者至少可以节省我的时间)。谢谢。
Dan Nissenbaum

1
如果我按照答案中的方法运行,那么它将无法递归工作。为了递归进行此工作,我不得不将其更改为:glob.iglob(root_dir + '**/**', recursive=True)。我正在使用Python 3.8.2
mikey,

38

同意Dave Webb,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

因此它循环了3次,因为有3个文件夹:当前文件夹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']

因此,在第一行中,我们看到我们在folder中.,它包含两个文件夹即picsdocs,并且存在一个文件,即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像这样对对象执行递归glob 。

from pathlib import Path

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

6

如果要给定目录下所有路径的平面列表(如find .在shell中):

   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默认情况下不会递归遍历。对于每个目录,从根目录开始都会生成一个三元组(目录路径,目录名,文件名)

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 2012年

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

当已经将目录列表从walk()拆分为文件和目录时,为什么还要先执行另一个listdir()然后再执行isdir()?这似乎在大树中会相当慢(执行三个syscall而不是一个:1 = walk,2 = listdir,3 = isdir,而不仅仅是遍历和循环遍历“ subdirs”和“ files”)。
卢克,

0

我认为问题是您没有在处理 os.walk正确。

首先,更改:

filePath = rootdir + '/' + file

至:

filePath = root + '/' + file

rootdir是您的固定起始目录;root是由返回的目录os.walk

其次,您不需要缩进文件处理循环,因为对每个子目录运行此循环都没有意义。您将root设置到每个子目录。除非您要对目录本身进行某些操作,否则无需手动处理子目录。


每个子目录中都有数据,因此每个目录的内容都需要一个单独的文本文件。
Brock Woolf'2

@Brock:文件部分是当前目录中文件的列表。因此,缩进确实是错误的。您正在写filePath = rootdir + '/' + file,听起来不对:文件来自当前文件列表,所以您要写很多现有文件?
Alok Singhal'2
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.