Folder rekursif Python baca

225

Saya memiliki latar belakang C ++ / Obj-C dan saya baru saja menemukan Python (telah menulisnya sekitar satu jam). Saya sedang menulis skrip untuk secara rekursif membaca isi file teks dalam struktur folder.

Masalah yang saya miliki adalah kode yang saya tulis hanya akan berfungsi untuk satu folder. Saya bisa melihat mengapa dalam kode (lihat #hardcoded path), saya hanya tidak tahu bagaimana saya bisa bergerak maju dengan Python karena pengalaman saya dengan itu hanya baru.

Kode 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()
Brock Woolf
sumber

Jawaban:

347

Pastikan Anda memahami tiga nilai pengembalian os.walk:

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

memiliki arti sebagai berikut:

  • root: Jalan saat ini yang "dilalui"
  • subdirs: File dalam rootdirektori tipe
  • files: File dalam root(bukan di subdirs) dari jenis selain direktori

Dan tolong gunakan os.path.joinalih-alih digabungkan dengan garis miring! Masalah Anda adalah filePath = rootdir + '/' + file- Anda harus menggabungkan folder "berjalan" saat ini dan bukan folder paling atas. Pasti begitu filePath = os.path.join(root, file). "File" BTW adalah builtin, jadi Anda biasanya tidak menggunakannya sebagai nama variabel.

Masalah lain adalah loop Anda, yang seharusnya seperti ini, misalnya:

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

Jika Anda tidak tahu, withpernyataan untuk file adalah singkatan:

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

# is effectively the same as

f = open('filename', 'rb')
try:
    dosomething()
finally:
    f.close()
AndiDog
sumber
4
Luar biasa, banyak cetakan untuk memahami apa yang sedang terjadi dan bekerja dengan sempurna. Terima kasih! +1
Brock Woolf
16
Menuju siapa pun yang bodoh / tidak sadar seperti saya ... contoh kode ini menulis file txt ke setiap direktori. Senang saya mengujinya dalam folder versi terkontrol, meskipun semua yang saya butuhkan untuk menulis skrip pembersihan ada di sini juga :)
Steazy
cuplikan kode kedua (terlama) bekerja dengan sangat baik, menyelamatkan saya banyak pekerjaan yang membosankan
amfibi
1
Karena kecepatan jika jelas aspek yang paling penting, os.walktidak buruk, meskipun saya datang dengan cara yang lebih cepat os.scandir. Semua globsolusi jauh lebih lambat daripada walk& scandir. Fungsi saya, serta analisis kecepatan lengkap, dapat ditemukan di sini: stackoverflow.com/a/59803793/2441026
user136036
112

Jika Anda menggunakan Python 3.5 atau lebih tinggi, Anda bisa menyelesaikannya dalam 1 baris.

import glob

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

Seperti disebutkan dalam dokumentasi

Jika rekursif benar, pola '**' akan cocok dengan file apa pun dan nol atau lebih direktori dan subdirektori.

Jika Anda ingin setiap file, Anda dapat menggunakan

import glob

for filename in glob.iglob(root_dir + '**/*', recursive=True):
     print(filename)
ChillarAnand
sumber
TypeError: iglob () mendapat argumen kata kunci tak terduga 'rekursif'
Jewenile
1
Seperti disebutkan di awal, itu hanya untuk Python 3.5+
ChillarAnand
9
root_dir harus memiliki garis miring (jika tidak Anda mendapatkan sesuatu seperti 'folder ** / *' daripada 'folder / ** / *' sebagai argumen pertama). Anda dapat menggunakan os.path.join (root_dir, ' * / '), tetapi saya tidak tahu apakah dapat menggunakan os.path.join dengan jalur wildcard (meskipun ini berfungsi untuk aplikasi saya).
drojf
@ChillarAnand Bisakah Anda menambahkan komentar ke kode dalam jawaban ini yang root_dirmembutuhkan garis miring? Ini akan menghemat waktu orang (atau setidaknya itu akan menghemat waktu saya). Terima kasih.
Dan Nissenbaum
1
Jika saya menjalankan ini seperti pada jawaban itu tidak bekerja secara rekursif. Untuk membuat karya ini secara rekursif saya harus mengubahnya ke: glob.iglob(root_dir + '**/**', recursive=True). Saya bekerja di Python 3.8.2
mikey
38

Setuju dengan Dave Webb, os.walkakan menghasilkan item untuk setiap direktori di pohon. Faktanya adalah, Anda tidak perlu peduli subFolders.

Kode seperti ini seharusnya berfungsi:

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())
Sejuk
sumber
3
Bagus Ini juga berfungsi. Namun saya lebih suka versi AndiDog meskipun lebih lama karena lebih jelas untuk dipahami sebagai pemula untuk Python. +1
Brock Woolf
20

TL; DR: Ini sama dengan find -type funtuk memeriksa semua file di semua folder di bawah ini dan termasuk yang sekarang:

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

Seperti yang sudah disebutkan dalam jawaban lain, os.walk()adalah jawabannya, tetapi bisa dijelaskan lebih baik. Sederhana saja! Mari kita berjalan melalui pohon ini:

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

Dengan kode ini:

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

Ini currentpathadalah folder yang sedang dilihatnya. Ini akan menampilkan:

.
./docs
./pics

Jadi loop tiga kali, karena ada tiga folder: yang sekarang docs,, dan pics. Di setiap loop, ia mengisi variabel foldersdan filesdengan semua folder dan file. Mari kita tunjukkan pada mereka:

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

Ini menunjukkan kepada kita:

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

Jadi di baris pertama, kita melihat bahwa kita berada di folder ., yang berisi dua folder yaitu picsdan docs, dan bahwa ada satu file, yaitu todo.txt. Anda tidak perlu melakukan apa pun untuk berulang ke folder itu, karena seperti yang Anda lihat, itu berulang secara otomatis dan hanya memberi Anda file dalam subfolder. Dan subfolder apa pun dari itu (meskipun kami tidak memiliki mereka dalam contoh).

Jika Anda hanya ingin mengulang semua file, yang setara dengan find -type f, Anda dapat melakukan ini:

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

Output ini:

./todo.txt
./docs/doc1.odt
Luc
sumber
9

The pathlibperpustakaan benar-benar hebat untuk bekerja dengan file. Anda dapat melakukan gumpalan rekursif pada Pathobjek seperti itu.

from pathlib import Path

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

Jika Anda ingin daftar datar semua jalur di bawah direktori tertentu (seperti find .di shell):

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

Untuk hanya menyertakan path lengkap ke file di bawah dir base, tinggalkan + subdirs.

Scott Smith
sumber
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())

**/**digunakan untuk mendapatkan semua file secara rekursif termasuk directory.

if os.path.isfile(filename)digunakan untuk memeriksa apakah filenamevariabel adalah fileatau directory, apakah itu file maka kita dapat membaca file itu. Di sini saya mencetak file.

Neeraj Sonaniya
sumber
6

Saya menemukan yang berikut ini sebagai yang termudah

from glob import glob
import os

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

Menggunakan glob('some/path/**', recursive=True)mendapat semua file, tetapi juga termasuk nama direktori. Menambahkan if os.path.isfile(f)kondisi ini memfilter daftar ini hanya untuk file yang ada

Michael Silverstein
sumber
3

gunakan os.path.join()untuk membangun jalur Anda - Lebih rapi:

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
sumber
Sepertinya kode ini hanya berfungsi untuk folder 2 level (atau lebih dalam). Tetap saja itu membuat saya lebih dekat.
Brock Woolf
1

os.walktidak berjalan rekursif secara default. Untuk setiap dir, mulai dari root menghasilkan 3-tupel (dirpath, dirnames, nama file)

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
sumber
1
Dalam Python 2.6 walk() jangan kembali daftar rekursif. Saya mencoba kode Anda dan mendapat daftar dengan banyak pengulangan ... Jika Anda hanya menghapus baris di bawah komentar "# panggilan rekursif pada subfolder" - berfungsi dengan baik
borisbn
1

Coba ini:

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
Diego
sumber
Mengapa Anda melakukan listdir lain () dan kemudian isdir () ketika Anda sudah memiliki daftar direktori dipecah menjadi file dan direktori dari walk ()? Ini kelihatannya agak lambat di pohon besar (lakukan tiga syscall alih-alih satu: 1 = berjalan, 2 = listdir, 3 = isdir, daripada hanya berjalan dan memutar melalui 'subdir' dan 'file').
Luc
0

Saya pikir masalahnya adalah Anda tidak memproses output os.walkdengan benar.

Pertama, ubah:

filePath = rootdir + '/' + file

untuk:

filePath = root + '/' + file

rootdiradalah direktori awal tetap Anda; rootadalah direktori yang dikembalikan oleh os.walk.

Kedua, Anda tidak perlu membuat indentasi loop pemrosesan file Anda, karena tidak masuk akal untuk menjalankan ini untuk setiap subdirektori. Anda akan rootdiatur ke setiap subdirektori. Anda tidak perlu memproses subdirektori dengan tangan kecuali jika Anda ingin melakukan sesuatu dengan direktori itu sendiri.

Dave Webb
sumber
Saya memiliki data di setiap sub direktori, jadi saya perlu memiliki file teks terpisah untuk isi setiap direktori.
Brock Woolf
@ Batu: bagian file adalah daftar file di direktori saat ini. Jadi lekukan memang salah. Anda menulis filePath = rootdir + '/' + file, itu kedengarannya tidak benar: file berasal dari daftar file saat ini, jadi Anda menulis ke banyak file yang ada?
Alok Singhal