在Python中将带BOM的UTF-8转换为不带BOM的UTF-8

112 投票
7 回答
179920 浏览
提问于 2025-04-17 10:27

这里有两个问题。我有一组文件,通常是带有BOM的UTF-8格式。我想把它们(最好是在原位置)转换成不带BOM的UTF-8格式。看起来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

另外,如果我们能处理不同的输入编码,而不需要明确知道(比如ASCII和UTF-16),那就更理想了。看起来这应该是可行的。有没有一种解决方案,可以接受任何已知的Python编码,并输出为不带BOM的UTF-8格式呢?

编辑 1 下面提出的解决方案(谢谢!)

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',所以我应该最终重新编辑我的问题,去掉已解决的部分。

7 个回答

8

在编程中,有时候我们需要把一些数据从一个地方转移到另一个地方。这就像把水从一个杯子倒到另一个杯子一样。这个过程可能会涉及到不同的步骤,比如选择要转移的数据、确定目标位置等。

在这个过程中,我们可能会用到一些工具或方法来帮助我们完成这个任务。这些工具就像是我们在厨房里用的各种器具,帮助我们更方便地做事情。

有时候,转移数据的过程可能会出现一些问题,比如数据格式不匹配,或者目标位置没有足够的空间来存放这些数据。这就像我们在倒水的时候,如果目标杯子太小,水就会溢出来。

为了避免这些问题,我们需要提前做好准备,确保一切都能顺利进行。这就像在倒水之前,先检查一下杯子的大小和形状,确保它能接住所有的水。

总之,数据转移是一个需要仔细考虑和规划的过程,确保每一步都能顺利完成。

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)
92

在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)
163

这个回答是针对Python 2的

你可以简单地使用“utf-8-sig”编码

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

这样你就能得到一个没有BOM的unicode字符串。接着你可以使用

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

把它转换回正常的UTF-8编码字符串,存放在s里。如果你的文件很大,最好不要一次性把它们全部读入内存。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个字节。文件会在原地被重写。一个更简单的办法是把处理后的内容写入一个新文件,就像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——这总是有效,因为Latin-1的256个字节都是合法值。在这种情况下,你可能想返回None,因为这实际上是一个后备方案,你的代码可能需要更小心地处理这种情况(如果可以的话)。

撰写回答