1. ホーム
  2. python

[解決済み] PythonでBOM付きのUTF-8をBOM無しのUTF-8に変換する。

2023-03-03 22:34:49

質問

ここで2つの質問があります。私は、通常 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

ニュースフラッシュ

コメントで、私が 'r+'/'r+b' ではなく 'rw' モードでファイルを開いたのが間違いだと言われているので、結局私の質問を再編集して解決した部分を削除する必要があります。

どのように解決するのですか?

単純に utf-8-sig" コーデックを使用します。 :

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

これによって unicode 文字列が得られます。次に

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

で通常のUTF-8エンコードされた文字列を取得します。 s . もしファイルが大きいなら、それらをすべてメモリに読み込むことは避けるべきです。BOM は単にファイルの先頭にある 3 バイトなので、このコードを使ってファイルからそれらを取り除くことができます。

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の回答 . これはより簡単ですが、短時間で2倍のディスクスペースを使用します。

エンコーディングの推測に関しては、最も特定的なものから最も特定的でないものへとループさせればよいのです。

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 なぜなら、これは本当にフォールバックであり、あなたのコードは(可能であれば)より慎重にこれを処理したいと思うかもしれないからです。