在Python中不使用BOM将UTF-8与BOM转换为UTF-8

这里有两个问题。 我有一套通常是带有BOM的UTF-8的文件。 我想将它们(理想情况下)转换为UTF-8,不含BOM。 看起来像codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors)将处理此。 但是我没有看到使用过程中的任何好例子。 这是处理这个问题的最好方法吗?

 source files: Tue Jan 17$ file brh-m-157.json brh-m-157.json: UTF-8 Unicode (with BOM) text 

另外,如果我们能够处理不同的input编码而不明确地知道(见ASCII和UTF-16),那将是理想的。 看来这应该都是可行的。 是否有解决scheme,可以采取任何已知的Python编码和输出为UTF-8没有BOM?

编辑1从下面sol'n(谢谢!)

 fp = open('brh-m-157.json','rw') s = fp.read() u = s.decode('utf-8-sig') s = u.encode('utf-8') print fp.encoding fp.write(s) 

这给了我以下错误:

 IOError: [Errno 9] Bad file descriptor 

新闻快报

我被告知在评论中,错误是我用模式'rw'而不是'r +'/'r + b'打开文件,所以我最终应该重新编辑我的问题,并删除解决的部分。

只需使用“utf-8-sig”编解码器即可:

 fp = open("file.txt") s = fp.read() u = s.decode("utf-8-sig") 

这给你一个没有BOM的unicodestring。 你可以使用

 s = u.encode("utf-8") 

s得到一个正常的UTF-8编码的string。 如果你的文件很大,那么你应该避免将它们全部读入内存。 BOM只是在文件开头的三个字节,所以你可以使用这段代码将它们从文件中除去:

 import os, sys, codecs BUFSIZE = 4096 BOMLEN = len(codecs.BOM_UTF8) path = sys.argv[1] with open(path, "r+b") as fp: chunk = fp.read(BUFSIZE) if chunk.startswith(codecs.BOM_UTF8): i = 0 chunk = chunk[BOMLEN:] while chunk: fp.seek(i) fp.write(chunk) i += len(chunk) fp.seek(BOMLEN, os.SEEK_CUR) chunk = fp.read(BUFSIZE) fp.seek(-BOMLEN, os.SEEK_CUR) fp.truncate() 

它打开文件,读取一个块,并将其写入文件比读取它的地方早3个字节。 该文件被重写就地。 更简单的解决scheme是将较短的文件写入新文件,如newtover的答案 。 这会更简单,但在短时间内使用两倍的磁盘空间。

至于猜测编码,那么你可以循环编码从最多到最不具体的:

 def decode(s): for encoding in "utf-8-sig", "utf-16": try: return s.decode(encoding) except UnicodeDecodeError: continue return s.decode("latin-1") # will always work 

一个UTF-16编码文件不会像UTF-8解码,所以我们先用UTF-8进行尝试。 如果失败了,那么我们试试UTF-16。 最后,我们使用Latin-1 – 这将始终工作,因为所有256个字节都是Latin-1中的合法值。 在这种情况下,您可能想要返回None ,因为它确实是一个回退,您的代码可能需要更仔细地处理(如果可以的话)。

在Python 3中很容易:读取文件并用utf-8编码重写:

 s = open(bom_file, mode='r', encoding='utf-8-sig').read() open(bom_file, mode='w', encoding='utf-8').write(s) 
 import codecs import shutil import sys s = sys.stdin.read(3) if s != codecs.BOM_UTF8: sys.stdout.write(s) shutil.copyfileobj(sys.stdin, sys.stdout) 

这是我的实现将任何types的编码转换为UTF-8没有BOM,并通过通用格式replace窗口enline:

 def utf8_converter(file_path, universal_endline=True): ''' Convert any type of file to UTF-8 without BOM and using universal endline by default. Parameters ---------- file_path : string, file path. universal_endline : boolean (True), by default convert endlines to universal format. ''' # Fix file path file_path = os.path.realpath(os.path.expanduser(file_path)) # Read from file file_open = open(file_path) raw = file_open.read() file_open.close() # Decode raw = raw.decode(chardet.detect(raw)['encoding']) # Remove windows end line if universal_endline: raw = raw.replace('\r\n', '\n') # Encode to UTF-8 raw = raw.encode('utf8') # Remove BOM if raw.startswith(codecs.BOM_UTF8): raw = raw.replace(codecs.BOM_UTF8, '', 1) # Write to file file_open = open(file_path, 'w') file_open.write(raw) file_open.close() return 0 

你可以使用编解码器。

 import codecs content = open("test.txt",'r').read() filehandle.close() if content[:3] == codecs.BOM_UTF8 content = content[3:] print content.decode("utf-8") 

最近我试图build立一些我以前工作过的旧的Android项目,但源文件中有BOM,无法编译。 有数以百计的文件,所以我试图寻找MAC操作系统上的一些工具,可以自动转换,但找不到任何,所以我写了一个Python脚本,从文件夹中的文件中删除BOM:

BomSweeper

希望这可以帮助你。

注意:脚本是基于@Martin Geisler的回答,我想给他的回答添加评论,但是我没有足够的声望去做,所以我只是创build一个新的答案。

我发现这个问题,因为遇到configparser.ConfigParser().read(fp)在打开带有UTF8 BOM头的文件时遇到问题。

对于那些正在寻找解决scheme来删除标题,以便ConfigPhaser可以打开configuration文件,而不是报告错误: File contains no section headers ,请打开文件,如下所示:

  configparser.ConfigParser().read(config_file_path, encoding="utf-8-sig") 

通过删除文件的BOM标题不必要,这可以节省大量的工作。

(我知道这听起来不相关,但希望这可以帮助像我这样挣扎的人。)