将一堆文件从猜测的编码转换为 UTF-8

Convert a bunch of files from guessed encoding to UTF-8

我有 this Python script 试图检测文本文件的字符编码(在本例中,C# .cs 源文件,但它们可以是任何文本文件),然后将它们从该字符编码并转换为 UTF-8(无 BOM)。

虽然 chardet 检测编码足够好并且脚本运行没有错误,但像 © 这样的字符被编码为 $。所以我假设脚本和我对 Python 2 中编码的理解有问题。由于将文件从 UTF-8-SIG 转换为 UTF-8 有效,我觉得问题出在解码(读)部分而不是编码(写)部分。

谁能告诉我我做错了什么?如果切换到 Python 3 是一个解决方案,我完全赞成,然后我只需要帮助弄清楚如何将脚本从版本 2.7 的 运行 转换为 3.4。这是脚本:

import os
import glob
import fnmatch
import codecs
from chardet.universaldetector import UniversalDetector

# from http://farmdev.com/talks/unicode/
def to_unicode_or_bust(obj, encoding='utf-8'):
    if isinstance(obj, basestring):
        if not isinstance(obj, unicode):
            obj = unicode(obj, encoding)
    return obj

def enforce_unicode():
    detector = UniversalDetector()

    for root, dirnames, filenames in os.walk('.'):
      for filename in fnmatch.filter(filenames, '*.cs'):
        detector.reset()
        filepath = os.path.join(root, filename)

        with open(filepath, 'r') as f:
            for line in f:
                detector.feed(line)
                if detector.done: break

        detector.close()
        encoding = detector.result['encoding']

        if encoding and not encoding == 'UTF-8':
            print '%s -> UTF-8   %s' % (encoding.ljust(12), filepath)
            with codecs.open(filepath, 'r', encoding=encoding) as f:
                content = ''.join(f.readlines())

            content = to_unicode_or_bust(content)

            with codecs.open(filepath, 'w', encoding='utf-8') as f:
                f.write(content)

enforce_unicode()

我曾尝试在写入文件之前执行 content = content.decode(encoding).encode('utf-8'),但失败并出现以下错误:

/usr/local/.../lib/python2.7/encodings/utf_8_sig.py:19: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
  if input[:3] == codecs.BOM_UTF8:
Traceback (most recent call last):
  File "./enforce-unicode.py", line 48, in <module>
    enforce_unicode()
  File "./enforce-unicode.py", line 43, in enforce_unicode
    content = content.decode(encoding).encode('utf-8')
  File "/usr/local/.../lib/python2.7/encodings/utf_8_sig.py", line 22, in decode
    (output, consumed) = codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa9' in position 87: ordinal not in range(128)

想法?

chardet 只是将检测到的编解码器弄错了,否则您的代码是正确的。字符检测基于统计、启发式和简单猜测,它不是万无一失的方法。

例如,Windows 1252 codepage 非常接近 Latin-1 编解码器;使用一种编码编码的文件可以在另一种编码中无错误地解码。检测一个中的控制代码或另一个中的欧元符号之间的差异通常需要人工查看结果。

我会记录每个文件的 chardet 猜测,如果文件被错误地重新编码,您需要查看其他编解码器可能会关闭。所有 1250 系列代码页看起来都很相似。