Python再帰フォルダー読み取り


225

私はC ++ / Obj-Cのバックグラウンドを持っており、Pythonを発見しているだけです(1時間ほど書いています)。フォルダー構造内のテキストファイルの内容を再帰的に読み取るスクリプトを作成しています。

私が持っている問題は、私が書いたコードが1つのフォルダーの深さでしか機能しないことです。コード(を参照#hardcoded path)で理由を確認できますが、Pythonでの経験はまったく新しいものなので、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

の3つの戻り値を必ず理解してください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)。ところで、「ファイル」は組み込みなので、通常は変数名として使用しません。

もう1つの問題はループです。たとえば、次のようになります。

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

秒(最長)コードスニペットは、私の退屈な仕事をたくさん保存され、非常によく働いたこと
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)

ドキュメントで述べたように

再帰がtrueの場合、パターン「**」は、すべてのファイルと0個以上のディレクトリおよびサブディレクトリに一致します。

すべてのファイルが必要な場合は、

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をワイルドカードパスで使用することが許容されるかどうかはわかりません(ただし、アプリケーションで機能します)。
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
良いですね。これも機能します。ただし、Pythonの初心者として理解するのがより明確になるため、AndiDogのバージョンはより長くても、私は好みます。+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およびの3つのフォルダがあるため、3回ループしますpics。すべてのループで、変数foldersfilesすべてのフォルダーとファイルを入力します。それらを見てみましょう:

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

これは私たちを示しています:

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

したがって、最初の行では、folder .にいることがわかります。これには、2つのフォルダー、つまりpicsとが含まれdocs、1つのファイル、つまりがあり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

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ます。ファイルの場合、そのファイルを読み取ることができます。ここでファイルを印刷しています。


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レベル(またはそれ以上)のフォルダーでのみ機能するようです。それでも私は近づきます。
Brock Woolf

1

os.walkデフォルトでは再帰的にウォークします。各dirについて、ルートから始めて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

1
Python 2.6 walk() では、再帰的なリストを返します。私はあなたのコードを試して、多くの繰り返しのあるリストを得ました...コメント「#サブフォルダーの再帰呼び出し」の下の行を削除するだけなら-それはうまく
いき

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()を実行するのですか?これは、大きなツリーではかなり遅くなるようです(1つではなく3つのシステムコールを実行します:1 = walk、2 = listdir、3 = isdir、単に「subdirs」と「files」をウォークしてループするのではなく)。
Luc、

0

問題は、出力をos.walk正しく処理していないことだと思います。

まず、変更します。

filePath = rootdir + '/' + file

に:

filePath = root + '/' + file

rootdir固定の開始ディレクトリです。rootによって返されるディレクトリos.walkです。

次に、サブディレクトリごとにこれを実行しても意味がないため、ファイル処理ループをインデントする必要はありません。root各サブディレクトリに設定されます。ディレクトリ自体で何かを実行したい場合を除き、サブディレクトリを手動で処理する必要はありません。


各サブディレクトリにデータがあるので、各ディレクトリのコンテンツ用に個別のテキストファイルが必要です。
Brock Woolf

@ブロック:ファイルの部分は、現在のディレクトリにあるファイルのリストです。したがって、インデントは実際に間違っています。あなたはに書き込んでいますがfilePath = rootdir + '/' + file、それは正しく聞こえません:ファイルは現在のファイルのリストからのものなので、既存の多くのファイルに書き込んでいますか?
Alok Singhal、2010
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.