Python đọc thư mục đệ quy


225

Tôi có một nền tảng C ++ / Obj-C và tôi chỉ đang khám phá Python (đã viết nó trong khoảng một giờ). Tôi đang viết một kịch bản để đọc đệ quy nội dung của tệp văn bản trong cấu trúc thư mục.

Vấn đề tôi có là mã tôi đã viết sẽ chỉ hoạt động cho một thư mục sâu. Tôi có thể thấy lý do tại sao trong mã (xem #hardcoded path), tôi chỉ không biết làm thế nào tôi có thể tiến lên với Python vì kinh nghiệm của tôi với nó chỉ là hoàn toàn mới.

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

Câu trả lời:


347

Hãy chắc chắn rằng bạn hiểu ba giá trị trả về của os.walk:

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

có ý nghĩa như sau:

  • root: Con đường hiện tại được "đi qua"
  • subdirs: Tập tin trong rootthư mục loại
  • files: Các tệp trong root(không phải subdirs) thuộc loại khác ngoài thư mục

Và xin vui lòng sử dụng os.path.jointhay vì nối với một dấu gạch chéo! Vấn đề của bạn là filePath = rootdir + '/' + file- bạn phải ghép thư mục hiện tại "đi bộ" thay vì thư mục trên cùng. Vì vậy, phải được filePath = os.path.join(root, file). "Tập tin" BTW là một nội dung, vì vậy bạn thường không sử dụng nó làm tên biến.

Một vấn đề khác là các vòng lặp của bạn, ví dụ như thế này:

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

Nếu bạn không biết, withtuyên bố cho các tệp là một tốc ký:

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

# is effectively the same as

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

4
Tuyệt vời, rất nhiều bản in để hiểu những gì đang diễn ra và nó hoạt động hoàn hảo. Cảm ơn! +1
Brock Woolf

16
Đối đầu với bất kỳ ai ngu ngốc / lãng quên như tôi ... mẫu mã này ghi một tệp txt vào mỗi thư mục. Vui mừng tôi đã thử nghiệm nó trong một thư mục được kiểm soát phiên bản, mặc dù mọi thứ tôi cần để viết một kịch bản dọn dẹp cũng ở đây :)
Steazy

đoạn mã thứ hai (dài nhất) đó hoạt động rất tốt, giúp tôi tiết kiệm rất nhiều công việc nhàm chán
lưỡng cư

1
Vì tốc độ nếu rõ ràng là khía cạnh quan trọng nhất, os.walkkhông phải là xấu, mặc dù tôi đã nghĩ ra một cách thậm chí nhanh hơn thông qua os.scandir. Tất cả các globgiải pháp chậm hơn rất nhiều so với walk& scandir. Chức năng của tôi, cũng như phân tích tốc độ hoàn chỉnh, có thể được tìm thấy ở đây: stackoverflow.com/a/59803793/2441026
user136036

112

Nếu bạn đang sử dụng Python 3.5 trở lên, bạn có thể hoàn thành việc này trong 1 dòng.

import glob

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

Như đã đề cập trong tài liệu

Nếu đệ quy là đúng, mẫu '**' sẽ khớp với bất kỳ tệp nào và không hoặc nhiều thư mục và thư mục con.

Nếu bạn muốn mọi tập tin, bạn có thể sử dụng

import glob

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

LoạiError: iglob () có một đối số từ khóa bất ngờ 'đệ quy'
Jewenile

1
Như đã đề cập ở phần đầu, nó chỉ dành cho Python 3.5+
ChillarAnand

9
root_dir phải có dấu gạch chéo (nếu không, bạn sẽ nhận được một cái gì đó như 'thư mục ** / *' thay vì 'thư mục / ** / *' làm đối số đầu tiên). Bạn có thể sử dụng os.path.join (root_dir, ' * / '), nhưng tôi không biết liệu có thể chấp nhận sử dụng os.path.join với các đường dẫn ký tự đại diện hay không (mặc dù nó hoạt động cho ứng dụng của tôi).
drojf

@ChillarAnand Bạn có thể vui lòng thêm một nhận xét vào mã trong câu trả lời này root_dircần một dấu gạch chéo không? Điều này sẽ tiết kiệm thời gian của mọi người (hoặc ít nhất nó sẽ giúp tôi tiết kiệm thời gian). Cảm ơn.
Dan Nissenbaum

1
Nếu tôi chạy nó như trong câu trả lời thì nó không hoạt động đệ quy. Để thực hiện công việc này một cách đệ quy tôi đã phải thay đổi nó thành : glob.iglob(root_dir + '**/**', recursive=True). Tôi đang làm việc trong Python 3.8.2
mikey

38

Đồng ý với Dave Webb, os.walksẽ mang lại một mục cho mỗi thư mục trong cây. Thực tế là, bạn không cần phải quan tâm subFolders.

Mã như thế này sẽ hoạt động:

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
Đẹp một. Điều này làm việc như là tốt. Tuy nhiên, tôi thích phiên bản của AndiDog hơn mặc dù nó dài hơn vì dễ hiểu hơn khi mới bắt đầu với Python. +1
Brock Woolf

20

TL; DR: Điều này tương đương với find -type fviệc đi qua tất cả các tệp trong tất cả các thư mục bên dưới và bao gồm cả tệp hiện tại:

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

Như đã đề cập trong các câu trả lời khác, os.walk()là câu trả lời, nhưng nó có thể được giải thích tốt hơn. Nó khá đơn giản! Hãy đi bộ qua cây này:

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

Với mã này:

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

Đây currentpathlà thư mục hiện tại nó đang xem xét. Điều này sẽ xuất ra:

.
./docs
./pics

Vì vậy, nó lặp ba lần, bởi vì có ba thư mục: thư mục hiện tại docspics. Trong mỗi vòng lặp, nó sẽ điền vào các biến foldersfilesvới tất cả các thư mục và tệp. Hãy cho họ thấy:

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

Điều này cho chúng ta thấy:

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

Vì vậy, trong dòng đầu tiên, chúng ta thấy rằng chúng ta đang ở trong thư mục ., nó chứa hai thư mục cụ thể picsdocs, và có một tệp, cụ thể là todo.txt. Bạn không phải làm bất cứ điều gì để lặp lại vào các thư mục đó, vì như bạn thấy, nó sẽ tự động đệ quy và chỉ cung cấp cho bạn các tệp trong bất kỳ thư mục con nào. Và bất kỳ thư mục con nào trong đó (mặc dù chúng tôi không có những thứ đó trong ví dụ).

Nếu bạn chỉ muốn lặp qua tất cả các tệp, tương đương find -type f, bạn có thể làm điều này:

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

Kết quả này:

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

9

Các pathlibthư viện là thực sự tuyệt vời để làm việc với các tập tin. Bạn có thể thực hiện một quả cầu đệ quy trên một Pathđối tượng như vậy.

from pathlib import Path

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

6

Nếu bạn muốn một danh sách phẳng tất cả các đường dẫn trong một thư mục đã cho (như find .trong trình bao):

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

Để chỉ bao gồm các đường dẫn đầy đủ đến các tệp theo thư mục cơ sở, hãy bỏ qua + 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())

**/**được sử dụng để có được tất cả các tệp đệ quy bao gồm directory.

if os.path.isfile(filename) được sử dụng để kiểm tra nếu filename biến là filehay directory, nếu là tệp thì chúng ta có thể đọc tệp đó. Ở đây tôi đang in file.


6

Tôi đã tìm thấy những điều sau đây là dễ nhất

from glob import glob
import os

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

Sử dụng glob('some/path/**', recursive=True)được tất cả các tập tin, nhưng cũng bao gồm tên thư mục. Thêm if os.path.isfile(f)điều kiện chỉ lọc danh sách này vào các tệp hiện có


3

sử dụng os.path.join()để xây dựng đường dẫn của bạn - Nó gọn gàng hơn:

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

Có vẻ như mã này chỉ hoạt động cho các thư mục 2 cấp (hoặc sâu hơn). Tuy nhiên, nó làm tôi gần hơn.
Brock Woolf

1

os.walkkhông đi bộ đệ quy theo mặc định. Đối với mỗi thư mục, bắt đầu từ gốc, nó mang lại 3 tuple (dirpath, dirnames, tên tệp)

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
Trong Python 2.6 walk() làm trở lại danh sách đệ quy. Tôi đã thử mã của bạn và nhận được một danh sách với nhiều lần lặp lại ... Nếu bạn chỉ xóa các dòng dưới nhận xét "# cuộc gọi đệ quy trên các thư mục con" - nó hoạt động tốt
borboln

1

Thử cái này:

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

Tại sao bạn lại thực hiện một listdir () và sau đó là isdir () khi bạn đã có danh sách thư mục được chia thành các tệp và thư mục từ walk ()? Điều này có vẻ như sẽ khá chậm trong những cây lớn (thực hiện ba tòa nhà thay vì một: 1 = walk, 2 = listdir, 3 = isdir, thay vì chỉ đi bộ và lặp qua 'tập tin con' và 'tập tin').
Luc

0

Tôi nghĩ vấn đề là bạn không xử lý đầu ra os.walkchính xác.

Thứ nhất, thay đổi:

filePath = rootdir + '/' + file

đến:

filePath = root + '/' + file

rootdirlà thư mục bắt đầu cố định của bạn; rootlà một thư mục được trả về bởi os.walk.

Thứ hai, bạn không cần thụt vào vòng xử lý tệp của mình, vì sẽ không có ý nghĩa gì khi chạy cái này cho mỗi thư mục con. Bạn sẽ được rootthiết lập cho mỗi thư mục con. Bạn không cần xử lý các thư mục con bằng tay trừ khi bạn muốn làm gì đó với chính các thư mục.


Tôi có dữ liệu trong mỗi thư mục con, vì vậy tôi cần có một tệp văn bản riêng cho nội dung của từng thư mục.
Brock Woolf

@Brock: phần tập tin là danh sách các tập tin trong thư mục hiện tại. Vì vậy, thụt lề thực sự là sai. Bạn đang viết filePath = rootdir + '/' + file, điều đó không đúng: tập tin nằm trong danh sách các tập tin hiện tại, vì vậy bạn đang ghi vào rất nhiều tập tin hiện có?
Alok Singhal
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.