WindowsにおけるPython2系だとマルチバイト文字の処理がまだ微妙なので、普通にファイル列挙処理を書くと、ファイルパス内に特定の文字(「表」「ソ」など)が登場したときに列挙処理がうまく動かないことがある。いわゆる5C問題。
今回テストに用いるファイル一覧
C:/test
filelist.py
てすと/
a1.txt
a2.txt
表/
hyo1.txt
hyo2.txt
中の表/
hyo10.txt
hyo11.txt
普通にファイル列挙を書いたスクリプト
# -*- coding: utf-8 -*-
import os
SEP = os.sep
def filelist(dir_path):
for item in os.listdir(dir_path):
file_path = dir_path + SEP + item
print(file_path)
if os.path.isdir(file_path):
filelist(file_path)
# test
script_dir = os.path.dirname(os.path.abspath(__file__))
filelist(script_dir)
実行結果
C:\test\filelist.py
C:\test\てすと
C:\test\てすと\a1.txt
C:\test\てすと\a2.txt
C:\test\表
C:\test\表\表
「表」が含まれるパス内の列挙がうまくいっていない。
対策 (decodeによる対策)
@wonderful_panda さんに教えていただきました。
ファイルパスを取得した後に .decode('cp932') しておくと問題なくファイル列挙が行えます。
# -*- coding: utf-8 -*-
import os
SEP = os.sep
def filelist(dir_path):
for item in os.listdir(dir_path):
file_path = dir_path + SEP + item
print(file_path)
if os.path.isdir(file_path):
filelist(file_path)
# test
script_dir = os.path.dirname(os.path.abspath(__file__.decode('cp932')))
filelist(script_dir)
実行結果
C:\test\filelist.py
C:\test\てすと
C:\test\てすと\a1.txt
C:\test\てすと\a2.txt
C:\test\表
C:\test\表\hyo1.txt
C:\test\表\hyo2.txt
C:\test\表\中の表
C:\test\表\中の表\hyo10.txt
C:\test\表\中の表\hyo11.txt
「表」が含まれるパス内の列挙も正常に行えた。
対策 (旧。カレントディレクトリによる対策)
decode による対策のほうが圧倒的にスマートなのですが、それを教えていただく前に書いていた対策方法も残しておきます。
# -*- coding: utf-8 -*-
import os
SEP = os.sep
def filelist2(dir_path):
old_dir = os.getcwd()
os.chdir(dir_path) # カレントディレクトリを変更.
for item in os.listdir("."):
file_path = dir_path + SEP + item
print(file_path)
if os.path.isdir(item):
filelist2(file_path)
os.chdir(old_dir) # カレントディレクトリを元に戻す.
# test
script_dir = os.path.dirname(os.path.abspath(__file__))
filelist(script_dir)
os.listdir に対して、5C系の文字が含まれているパスを渡すと問題が生じるので、パスを直接渡す代わりにあらかじめカレントディレクトリを目的のパスに設定しておき、os.listdir にはカレントディレクトリを示す "."
を渡す。これにより列挙は正常に行える。
実行結果
C:\test\filelist.py
C:\test\てすと
C:\test\てすと\a1.txt
C:\test\てすと\a2.txt
C:\test\表
C:\test\表\hyo1.txt
C:\test\表\hyo2.txt
C:\test\表\中の表
C:\test\表\中の表\hyo10.txt
C:\test\表\中の表\hyo11.txt
「表」が含まれるパス内の列挙も正常に行えた。
Python3系について
Windows上においても Python 3.5 等では、今回のような対策を施さなくてもファイル列挙は正常に行えた。
マルチバイト文字列処理を行うことが分かっている新規プログラムであれば Python2系ではなくPython3系で作り始めるのが無難に思う。
おまけ:PHPにおける5C問題解決
Windows 上の PHP でも同様の手段(カレントディレクトリ変更)でファイル列挙うまくできないかと試してみたけどうまくいかなった。情報いろいろ漁ってみたけど、結論としてはPHPにおける解決策は「無い」っぽい?
- Issues with getting file name & absolute path - Laravel 5 PHP - Stack Overflow
-
directoryentry - how to iterate over non-English file names in PHP - Stack Overflow
This is not possible. It's a limitation of PHP. PHP uses the multibyte versions of Windows APIs; you're limited to the characters your codepage can represent.
-
Working with Japanese filenames in PHP 5.3 and Windows Vista? - Stack Overflow
If you set a breakpoint at readdir_r() in win32\readdir.c, you'll see that FindNextFile already returns a filename with question marks in place of the characters you want, so there's nothing you can do about it, apart from patching PHP itself.