web-dev-qa-db-ja.com

os.walk実行時のUnicodeDecodeError

エラーが発生します:

'ascii' codec can't decode byte 0x8b in position 14: ordinal not in range(128)

os.walkを実行しようとしたとき。このエラーは、ディレクトリ内の一部のファイルに0x8b(非utf8)文字が含まれているために発生します。ファイルはWindowsシステム(したがってutf-16ファイル名)からのものですが、ファイルをLinuxシステムにコピーし、python 2.7(Linuxで実行)を使用してディレクトリをトラバースしています。

ユニコードの開始パスをos.walkに渡そうとしましたが、生成されるすべてのファイルとディレクトリは、utf8以外の名前になるまでユニコード名ですが、何らかの理由で、それらの名前がユニコードに変換されず、次に、コードはutf-16名をチョークします。すべての不快な名前を手動で見つけて変更する以外に、問題を解決する方法はありますか?

Python2.7に解決策がない場合、ファイルツリーをトラバースして不正なファイル名をutf-8に変換する(utf8以外の文字を削除する)ことで修正するスクリプトをpython3で記述できますか? N.B.名前には0x8b以外にもutf8以外の文字が多数含まれているため、一般的な方法で機能する必要があります。

更新:0x8bがまだbtye char(有効なASCIIではない)にすぎないという事実は、それをさらに不可解にします。このような文字列をUnicodeに変換する際に問題があることを確認しましたが、Unicodeバージョンを直接作成できることを確認しました。ウィットに:

>>> test = 'a string \x8b with non-ascii'
>>> test
'a string \x8b with non-ascii'
>>> unicode(test)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0x8b in position 9: ordinal not in  range(128)
>>> 
>>> test2 = u'a string \x8b with non-ascii'
>>> test2
u'a string \x8b with non-ascii'

これが私が得ているエラーのトレースバックです:

80.         for root, dirs, files in os.walk(unicode(startpath)):
File "/usr/lib/python2.7/os.py" in walk
294.             for x in walk(new_path, topdown, onerror, followlinks):
File "/usr/lib/python2.7/os.py" in walk
294.             for x in walk(new_path, topdown, onerror, followlinks):
File "/usr/lib/python2.7/os.py" in walk
284.         if isdir(join(top, name)):
File "/usr/lib/python2.7/posixpath.py" in join
71.             path += '/' + b

Exception Type: UnicodeDecodeError at /admin/casebuilder/company/883/
Exception Value: 'ascii' codec can't decode byte 0x8b in position 14: ordinal not in range(128)

問題の根本は、listdirから返されたファイルのリスト(os.walkの276行目)で発生します。

names = listdir(top)

Chars> 128の名前は、非Unicode文字列として返されます。

19
Scott

この問題は、2つの基本的な問題から生じます。 1つ目は、Python 2.xのデフォルトのエンコーディングは 'ascii'であり、デフォルトのLinuxエンコーディングは 'utf8'であるという事実です。これらのエンコーディングは、次の方法で確認できます。

sys.getdefaultencoding() #python
sys.getfilesystemencoding() #OS

ディレクトリの内容を返すosモジュール関数、つまりos.walkとos.listdirが、ASCIIのみのファイル名と非ASCIIファイル名を含むファイルのリストを返す場合、ASCIIエンコードファイル名は自動的にユニコードに変換されます。他はそうではありません。したがって、結果は、unicodeオブジェクトとstrオブジェクトの組み合わせを含むリストになります。将来的に問題を引き起こす可能性があるのはstrオブジェクトです。これらはASCIIではないため、pythonは使用するエンコーディングを知る方法がなく、したがって自動的にUnicodeにデコードすることはできません。

したがって、os.path(dir、file)などの一般的な操作を実行する場合、dirはユニコード、fileはエンコードされますstr、ファイルがASCIIエンコードされていない場合(デフォルト)、この呼び出しは失敗します。解決策は、各ファイル名が取得されるとすぐにチェックし、str(エンコードされたもの)オブジェクトを適切なエンコードを使用してユニコードにデコードすることです。

それが最初の問題とその解決策です。 2番目は少しトリッキーです。ファイルは元々Windowsシステムからのものであるため、ファイル名はおそらくwindows-1252と呼ばれるエンコーディングを使用します。確認する簡単な方法は、次の電話をかけることです。

filename.decode('windows-1252')

有効なUnicodeバージョンが得られた場合は、おそらく正しいエンコーディングを使用しています。 Unicodeバージョンでもprintを呼び出すことでさらに確認し、正しいファイル名がレンダリングされていることを確認できます。

最後のしわ。 Windows Originのファイルを含むLinuxシステムでは、windows-1252utf8が混在している可能性があります。エンコーディング。この混合物に対処するには2つの方法があります。最初の、そして望ましいのは、以下を実行することです。

$ convmv -f windows-1252 -t utf8 -r DIRECTORY --notest

ここで、DIRECTORYは、変換が必要なファイルを含むファイルです。このコマンドは、windows-1252でエンコードされたファイル名をutf8に変換します。ファイル名がすでにutf8(またはascii)である場合、何もしないという点で、スマートな変換を行います。

別の方法(何らかの理由でこの変換を実行できない場合)は、Pythonでその場で同様のことを実行することです。ウィットに:

def decodeName(name):
    if type(name) == str: # leave unicode ones alone
        try:
            name = name.decode('utf8')
        except:
            name = name.decode('windows-1252')
    return name

この関数は、最初にutf8デコードを試行します。失敗した場合は、windows-1252バージョンにフォールバックします。ファイルのリストを返すos呼び出しの後に、この関数を使用します。

root, dirs, files = os.walk(path):
    files = [decodeName(f) for f in files]
    # do something with the unicode filenames now

この素晴らしくて単純なチュートリアルを読むまで、私は個人的にユニコードとエンコーディングの主題全体が非常に混乱していることに気づきました。

http://farmdev.com/talks/unicode/

Unicodeの問題に苦しんでいる人には強くお勧めします。

5
Scott

ちょうど私はこのエラーを分類するのに少し時間を費やしました、そしてここでのより言葉の多い答えは根本的な問題に到達していません:

問題は、Unicode文字列os.walk()に渡すと、os.walkがos.listdir()からUnicodeを取得し始めてASCII(したがって、 'ascii'デコードエラー)として保持しようとします。str()が変換できないUnicodeのみの特殊文字にヒットすると、例外がスローされます。

solutionは、os.walkに渡す開始パスを強制的に通常の文字列にすることです-つまり、os.walk(str(somepath))です。これは、os.listdirが通常のバイトのような文字列を返し、すべてが正常に機能することを意味します。

この問題を次のように簡単に再現できます(そして、その解決策が機能することを示すことができます)。

  1. いくつかのディレクトリのbashに移動し、touch $(echo -e "\x8b\x8bThis is a bad filename")を実行すると、いくつかのテストファイルが作成されます。

  2. ここで、同じディレクトリで次のPythonコード(iPython Qtが便利です)を実行します。

    l = []
    for root,dir,filenames in os.walk(unicode('.')):
        l.extend([ os.path.join(root, f) for f in filenames ])
    print l
    

そして、UnicodeDecodeErrorが発生します。

  1. 今すぐ実行してみてください:

    l = []
    for root,dir,filenames in os.walk('.'):
        l.extend([ os.path.join(root, f) for f in filenames ])
    print l
    

エラーはなく、印刷されます。

したがって、Python 2.xの安全な方法は、生のテキストのみをos.walk()に渡すようにすることです。絶対にすべきではありません内部ASCII変換が失敗すると、os.walkがチョークするため、ユニコードまたはユニコードである可能性のあるものを渡します。

8
Will Rouesnel

os.listdir()の動作を再現できます。os.listdir(unicode_name)は、デコードできないエントリをバイトとしてPython 2.7:

_>>> import os
>>> os.listdir(u'.')
[u'abc', '<--\x8b-->']
_

注意:listdir()の引数がUnicode文字列であるにもかかわらず、2番目の名前はバイト文字列です。

ただし、大きな問題が残っています。このハックに頼らずに、これをどのように解決できるでしょうか。

Python 3は、surrogateescapeエラーハンドラー( _os.fsencode/os.fsdecode_ )を介して、ファイル名のデコード不可能なバイト(ファイルシステムの文字エンコードを使用)バイトを解決します。 PEP-383:システム文字インターフェイスのデコード不可能なバイト :を参照してください。

_>>> os.listdir(u'.')
['abc', '<--\udc8b-->']
_

注意:両方の文字列はUnicode(Python 3)です。そして、surrogateescapeエラーハンドラーが2番目の名前に使用されました。元のバイトを元に戻すには:

_>>> os.fsencode('<--\udc8b-->')
b'<--\x8b-->'
_

Python 2では、Windows(Unicode API)、OS X(utf-8が適用されます)ではファイル名にUnicode文字列を使用し、Linuxやその他のシステムではバイト文字列を使用します。

6
jfs

\ x8は有効なutf-8エンコーディング文字ではありません。 os.pathは、ファイル名がutf-8であることを想定しています。無効なファイル名にアクセスする場合は、os.path.walkに非Unicodeスタートパスを渡す必要があります。このように、osモジュールはutf8デコードを行いません。自分でそれを行い、間違った文字を含むファイル名をどうするかを決める必要があります。

つまり:

for root, dirs, files in os.walk(startpath.encode('utf8')):
1
ondra

エラーの原因を調べた後、Cコードルーチンlistdir内で何かが発生し、標準のASCIIではない場合に非Unicodeファイル名が返されます。したがって、唯一の修正は、os​​.walk内のディレクトリリストの強制デコードを実行することです。これには、os.walkの置き換えが必要です。この置換機能は機能します:

def asciisafewalk(top, topdown=True, onerror=None, followlinks=False):
    """
    duplicate of os.walk, except we do a forced decode after listdir
    """
    islink, join, isdir = os.path.islink, os.path.join, os.path.isdir

    try:
        # Note that listdir and error are globals in this module due
        # to earlier import-*.
        names = os.listdir(top)
        # force non-ascii text out
        names = [name.decode('utf8','ignore') for name in names]
    except os.error, err:
        if onerror is not None:
            onerror(err)
        return

    dirs, nondirs = [], []
    for name in names:
        if isdir(join(top, name)):
            dirs.append(name)
        else:
            nondirs.append(name)

    if topdown:
        yield top, dirs, nondirs
    for name in dirs:
        new_path = join(top, name)
        if followlinks or not islink(new_path):
            for x in asciisafewalk(new_path, topdown, onerror, followlinks):
                yield x
    if not topdown:
        yield top, dirs, nondirs

次の行を追加することにより:names = [name.decode( 'utf8'、 'ignore')for name in names]すべての名前は適切なASCIIおよびUnicodeであり、すべてが正しく機能します。

ただし、大きな問題が残っています。このハックに頼らずに、これをどのように解決できるでしょうか。

1
Scott

中国語(Unicode)名のディレクトリでos.walkを使用すると、この問題が発生しました。私は次のように自分でwalk関数を実装しました。これは、Unicodeのdir /ファイル名で正常に機能しました。

import os

ft = list(Tuple())

def walk(dir, cur):
    fl = os.listdir(dir)
    for f in fl:
        full_path = os.path.join(dir,f)    
        if os.path.isdir(full_path):
            walk(full_path, cur)
        else:
            path, filename = full_path.rsplit('/',1)
            ft.append((path, filename, os.path.getsize(full_path)))
0
jcz