Python อ่านโฟลเดอร์แบบเรียกซ้ำ


225

ฉันมีพื้นหลัง C ++ / Obj-C และฉันเพิ่งค้นพบ Python (เขียนเป็นเวลาประมาณหนึ่งชั่วโมง) ฉันกำลังเขียนสคริปต์เพื่ออ่านเนื้อหาไฟล์ข้อความซ้ำ ๆ ในโครงสร้างโฟลเดอร์

ปัญหาที่ฉันมีคือรหัสที่ฉันเขียนจะใช้ได้กับโฟลเดอร์เดียวเท่านั้น ฉันเห็นได้ว่าทำไมในรหัส (ดู#hardcoded path) ฉันไม่รู้ว่าฉันจะก้าวไปข้างหน้ากับ 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- คุณต้องเชื่อมต่อโฟลเดอร์ "walked" ในปัจจุบันแทนโฟลเดอร์บนสุด 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
Brock Woolf

16
มุ่งหน้าไปยังทุกคนอย่างโง่ / หลงลืมเหมือนฉัน ... ตัวอย่างโค้ดนี้เขียนไฟล์ txt ไปยังแต่ละไดเรกทอรี ดีใจที่ผมทดสอบในโฟลเดอร์รุ่นควบคุมแม้ว่าทุกอย่างที่ฉันต้องเขียนสคริปต์ล้างอยู่ที่นี่มากเกินไป :)
Steazy

ส่วนรหัส (ที่ยาวที่สุด) ที่สองนั้นทำงานได้ดีมากช่วยฉันทำงานที่น่าเบื่อได้มาก
amphibient

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 () ได้รับการโต้แย้งคำหลักที่ไม่คาดคิด 'recursive'
Jewenile

1
ดังที่ได้กล่าวไว้ในตอนแรกมันมีไว้สำหรับ Python 3.5+ เท่านั้น
ChillarAnand

9
root_dir ต้องมีเครื่องหมายสแลช (มิฉะนั้นคุณจะได้รับบางอย่างเช่น 'folder ** / *' แทน 'folder / ** / *' เป็นอาร์กิวเมนต์แรก) คุณสามารถใช้ os.path.join (root_dir, ' * / ') แต่ฉันไม่รู้ว่ามันเป็นที่ยอมรับหรือไม่หากใช้ os.path.join กับพา ธ wildcard (ใช้ได้กับแอปพลิเคชันของฉัน)
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
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

ดังนั้นจึง loops สามครั้งเพราะมีสามโฟลเดอร์: หนึ่งในปัจจุบันและ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
   ]

ให้มีเฉพาะเส้นทางที่เต็มไปด้วยไฟล์ภายใต้ dir + 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()ในการสร้างเส้นทางของคุณ - มันเป็น neater:

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 ระดับ (หรือลึกกว่า) เท่านั้น ยังคงทำให้ฉันใกล้ชิด
Brock Woolf

1

os.walkการเรียกซ้ำโดยปริยาย สำหรับ dir แต่ละตัวเริ่มต้นจากรูทจะให้ผลลัพธ์ 3-tuple (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

1
ใน Python 2.6 walk() จะส่งคืน recursive list ฉันพยายามรหัสของคุณและมีรายชื่อซ้ำกับหลาย ๆ ... หากคุณเพียงแค่ลบบรรทัดภายใต้ความคิดเห็น "# โทร recursive ในโฟลเดอร์ย่อย" - มันทำงานได้ดี
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 () ดูเหมือนว่ามันจะค่อนข้างช้าในต้นไม้ขนาดใหญ่ (ทำ syscalls สามอันแทนหนึ่ง: 1 = walk, 2 = listdir, 3 = isdir แทนที่จะเดินและวนผ่าน 'subdir' และ 'files')
ลุค

0

ฉันคิดว่าปัญหาคือคุณไม่ได้ประมวลผลผลลัพธ์ของ os.walkถูกต้อง

ประการแรกเปลี่ยน:

filePath = rootdir + '/' + file

ถึง:

filePath = root + '/' + file

rootdirเป็นไดเรกทอรีเริ่มต้นคงที่ของคุณ; เป็นไดเรกทอรีกลับโดยrootos.walk

ประการที่สองคุณไม่จำเป็นต้องเยื้องการประมวลผลไฟล์ของคุณเพราะมันไม่มีเหตุผลที่จะเรียกใช้สิ่งนี้สำหรับแต่ละไดเรกทอรีย่อย คุณจะได้รับการrootตั้งค่าเป็นแต่ละไดเรกทอรีย่อย คุณไม่จำเป็นต้องดำเนินการไดเรกทอรีย่อยด้วยตนเองยกเว้นว่าคุณต้องการทำบางสิ่งกับไดเรกทอรีเอง


ฉันมีข้อมูลในแต่ละไดเรกทอรีย่อยดังนั้นฉันจึงจำเป็นต้องมีไฟล์ข้อความแยกต่างหากสำหรับเนื้อหาของแต่ละไดเรกทอรี
Brock Woolf

@Brock: ส่วนของไฟล์คือรายการของไฟล์ในไดเรกทอรีปัจจุบัน ดังนั้นการเยื้องจึงผิดแน่นอน คุณกำลังเขียนถึงfilePath = rootdir + '/' + file, ไม่ถูกต้อง: ไฟล์มาจากรายการไฟล์ปัจจุบันดังนั้นคุณกำลังเขียนไปยังไฟล์ที่มีอยู่จำนวนมากหรือไม่
Alok Singhal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.