145:ディレクトリ内のファイルを再帰的に処理
os モジュールの walk 関数を用いると、あるディレクトリ内のファイルを再帰的に探索することができます。
次のようにすると、ディレクトリ spam 内のファイルのパスが得られます。
import os
for root, dirs, files in os.walk(u'root_dir'):
for file_ in files:
full_path = os.path.join(root, file_)
print full_path
print full_path の部分を希望する処理内容に置き換えてください。
使用例
カレントディレクトリに含まれるファイルのファイル名先頭に文字列 python_recipe_ をつけます。
import os
for root, dirs, files in os.walk(u'.'):
for file_ in files:
oldfilename = file_
oldfilepath = os.path.join(root, oldfilename)
newfilename = u'python_recipe_' + oldfilename
newfilepath = os.path.join(root, newfilename)
os.rename(oldfilepath, newfilepath)
ディレクトリ root_dir に含まれる、拡張子 .txt のファイルををすべて読み込んで all.txt 1つにまとめます。
import os
with open(u'all.txt', 'w') as all_txt:
for root, dirs, files in os.walk(u'root_dir'):
for file_ in files:
if os.path.splitext(file_)[1] == u'.txt':
with open(os.path.join(root, file_)) as txt:
all_txt.write(txt.read())
ex_change 関数はディレクトリに含まれるファイルの拡張子を変更します。
import os
def ex_change(dir, ex):
for root, dirs, files in os.walk(dir):
for file_ in files:
oldfilename = file_
newfilename = u'.'.join([
os.path.splitext(oldfilename)[0],
ex])
oldfilepath = os.path.join(root, oldfilename)
newfilepath = os.path.join(root, newfilename)
os.rename(oldfilepath, newfilepath)
# 使用例: root_dir 内のファイルの拡張子を .mp3 に書き換える
ex_change(u'root_dir', u'mp3')
os.walk について
os.walk で得られるもの
os.walk は 3 要素のタプルを返すジェネレータです。それぞれ、「今調べている最中のディレクトリ名」「見つかったディレクトリのリスト」「見つかったファイルのリスト」です。
次のようなディレクトリ root_dir を調べるとこのようになります。
root_dir --- file0
|- file1
|
|- dir0 ---- file2
| |- file3
|
|- dir1 ---- file4
| |- file5
| |
| |- dir4 ---- file8
| |- file9
|
|- dir2
|
|- dir3 --- file6
|- file7
>>> for x in os.walk('root_dir'):
... print x
...
('root_dir', ['dir0', 'dir1', 'dir2', 'dir3'], ['file0', 'file1'])
('root_dir\\dir0', [], ['file2', 'file3'])
('root_dir\\dir1', ['dir4'], ['file4', 'file5'])
('root_dir\\dir1\\dir4', [], ['file8', 'file9'])
('root_dir\\dir2', [], [])
('root_dir\\dir3', [], ['file6', 'file7'])
探索順序
os.walk 関数には探索順を指定するための topdown 引数があります。True のときはトップダウンで、 False のときはボトムアップでディレクトリを探索します。デフォルト値は True です。
トップダウン時の特徴
今調べているディレクトリ内のファイルリストを先に作り、その後、各ディレクトリ内を調べに行きます。
ディレクトリ root_dir をトップダウンで探索した場合、次の順序でアクセスします。
root_dir root_dir\dir0 root_dir\dir1 root_dir\dir2 root_dir\dir3 root_dir\dir1\dir4
普段はこちらを使用するとよいでしょう。トップダウンで問題があるのはディレクトリに変更を加える処理をおこなうときです。これについてはボトムアップの項で説明します。
os.walk をトップダウンで使っているときは、処理中にディレクトリリストを変更することで、その後の探索対象を変更することができます。
'dir3' ディレクトリ以下を調べないようにするには dirs リストから 'dir3' を取り除きます。
>>> for root, dirs, files in os.walk(u'root_dir'): ... dirs[:] = (dir for dir in dirs if dir != u'dir3') ... for file_ in files: ... print os.path.join(root, file_) ... root_dir\file0 root_dir\file1 root_dir\dir0\file2 root_dir\dir0\file3 root_dir\dir1\file4 root_dir\dir1\file5 root_dir\dir1\dir4\file8 root_dir\dir1\dir4\file9
ボトムアップ時の特徴
今調べているディレクトリのファイルを後回しにして次の各ディレクトリ内を調べに行きます。
ディレクトリ root_dir ボトムアップで探索した場合、次の順序でアクセスします。
root_dir\dir1\dir4 root_dir\dir0 root_dir\dir1 root_dir\dir2 root_dir\dir3 root_dir
ボトムアップはディレクトリの変更作業などに向いています。
ためしに root_dir ディレクトリ内のすべてのディレクトリ名に _new を加える処理を今まで使っていたトップダウンで行い、どうなるのかを見てみましょう。
>>> for root, dirs, files in os.walk(u'root_dir', topdown=True): ... for dir in dirs: ... olddir = os.path.join(root, dir) ... newdir = os.path.join(root, dir + '_new') ... os.rename(olddir, newdir) ... print newdir ... root_dir\dir0_new root_dir\dir1_new root_dir\dir2_new root_dir\dir3_new
dir4 の処理が抜け落ちてしまっています。dir1 が dir1_new に先に変更されてしまうので、 dir1\dir4 も dir1_new\dir4 になります。このため dir1\dir4 はなくなってしまい、 dir1\dir4_new に変更することができなかったのです。
同様の処理をボトムアップで行ってみます。
>>> for root, dirs, files in os.walk('root_dir', topdown=False):
... for dir in dirs:
... olddir = os.path.join(root, dir)
... newdir = os.path.join(root, dir + '_new')
... os.rename(olddir, newdir)
... print newdir
...
root_dir\dir1\dir4_new
root_dir\dir0_new
root_dir\dir1_new
root_dir\dir2_new
root_dir\dir3_new
こちらは意図したとおりに動作します。
処理中のカレントディレクトリの変更
os.walk は実行中にカレントディレクトリが変更されることはないと仮定しています。 os.chdir などを使わないようにしてください。