Chuyển đổi UTF-8 có BOM thành UTF-8 không có BOM bằng Python


81

Hai câu hỏi ở đây. Tôi có một tập hợp các tệp thường là UTF-8 với BOM. Tôi muốn chuyển đổi chúng (lý tưởng là tại chỗ) thành UTF-8 mà không có BOM. Có vẻ như codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors)sẽ xử lý điều này. Nhưng tôi không thực sự thấy bất kỳ ví dụ tốt nào về cách sử dụng. Đây có phải là cách tốt nhất để xử lý điều này không?

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

Ngoài ra, sẽ là lý tưởng nếu chúng ta có thể xử lý các mã hóa đầu vào khác nhau một cách rõ ràng khi biết (xem ASCII và UTF-16). Có vẻ như điều này sẽ khả thi. Có giải pháp nào có thể nhận bất kỳ mã hóa và đầu ra Python nào đã biết dưới dạng UTF-8 mà không cần BOM không?

chỉnh sửa 1 sol'n được đề xuất từ ​​bên dưới (cảm ơ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)

Điều này mang lại cho tôi lỗi sau:

IOError: [Errno 9] Bad file descriptor

Newsflash

Tôi được cho biết trong các bình luận rằng lỗi là tôi mở tệp bằng chế độ 'rw' thay vì 'r +' / 'r + b', vì vậy cuối cùng tôi nên chỉnh sửa lại câu hỏi của mình và xóa phần đã giải.


2
Bạn cần mở tệp của mình để đọc cộng với cập nhật, tức là với một r+chế độ. Thêm bquá để nó cũng sẽ hoạt động trên Windows mà không có bất kỳ kinh doanh kết thúc dòng vui nhộn nào. Cuối cùng, bạn sẽ muốn quay lại phần đầu của tệp và cắt ngắn nó ở phần cuối - vui lòng xem câu trả lời cập nhật của tôi.
Martin Geisler

Câu trả lời:


123

Chỉ cần sử dụng codec "utf-8-sig" :

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

Điều đó cung cấp cho bạn một unicodechuỗi mà không có BOM. Sau đó bạn có thể sử dụng

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

để lấy lại một chuỗi được mã hóa UTF-8 bình thường s. Nếu các tệp của bạn lớn, thì bạn nên tránh đọc tất cả chúng vào bộ nhớ. BOM chỉ đơn giản là ba byte ở đầu tệp, vì vậy bạn có thể sử dụng mã này để tách chúng ra khỏi tệp:

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

Nó mở tệp, đọc một đoạn và ghi vào tệp sớm hơn 3 byte so với nơi nó đọc. Tệp được viết lại tại chỗ. Giải pháp dễ dàng hơn là ghi tệp ngắn hơn vào tệp mới như câu trả lời của newtover . Điều đó sẽ đơn giản hơn, nhưng sử dụng gấp đôi dung lượng đĩa trong một thời gian ngắn.

Đối với việc đoán mã hóa, sau đó bạn có thể lặp lại mã hóa từ hầu hết đến ít cụ thể nhất:

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

Tệp được mã hóa UTF-16 sẽ không giải mã thành UTF-8, vì vậy trước tiên chúng tôi thử với UTF-8. Nếu không thành công, thì chúng tôi thử với UTF-16. Cuối cùng, chúng tôi sử dụng Latin-1 - điều này sẽ luôn hoạt động vì tất cả 256 byte đều là giá trị pháp lý trong Latin-1. Bạn có thể muốn quay lại Nonethay thế trong trường hợp này vì nó thực sự là một dự phòng và mã của bạn có thể muốn xử lý điều này cẩn thận hơn (nếu có thể).


hmm, tôi đã cập nhật câu hỏi trong chỉnh sửa số 1 với mã mẫu nhưng nhận được bộ mô tả tệp kém. thx cho bất kỳ sự giúp đỡ. Cố gắng tìm ra điều này.
timpone

60

Trong Python 3, nó khá dễ dàng: đọc tệp và viết lại nó bằng utf-8mã hóa:

s = open(bom_file, mode='r', encoding='utf-8-sig').read()
open(bom_file, mode='w', encoding='utf-8').write(s)

1
Câu trả lời hay nhất trên web về chủ đề này. Chỉ cần sử dụng utf-8-sig.
QtRoS

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

bạn có thể giải thích cách mã này hoạt động không? $ remove_bom.py <input.txt> output.txt Tôi có đúng không?
guneysus

@guneysus, vâng, chính xác
newtover

1
tôi vừa mới thêmheader = header[3:] if header[0:3] == codecs.BOM_UTF8 else header
chinmayv

5

Đây là cách triển khai của tôi để chuyển đổi bất kỳ loại mã hóa nào sang UTF-8 mà không cần BOM và thay thế các đường viền cửa sổ bằng định dạng phổ quát:

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

2

Bạn có thể sử dụng codec.

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

hoàn toàn không sử dụng được đoạn mã (filehandle? cũng là codec.BOM_UTF8 trả về lỗi cú pháp)
Tối đa

2

Tôi tìm thấy câu hỏi này vì gặp sự cố configparser.ConfigParser().read(fp)khi mở tệp có tiêu đề UTF8 BOM.

Đối với những người đang tìm kiếm giải pháp xóa tiêu đề để ConfigPhaser có thể mở tệp cấu hình thay vì báo lỗi:, File contains no section headersvui lòng mở tệp như sau:

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

Điều này có thể giúp bạn tiết kiệm rất nhiều công sức bằng cách loại bỏ tiêu đề BOM của tệp không cần thiết.

(Tôi biết điều này nghe có vẻ không liên quan, nhưng hy vọng điều này có thể giúp những người đang gặp khó khăn như tôi.)


1
như lần đầu tiên tôi làm việc với thử - ngoại trừ -> điều này cũng mở các tệp được mã hóa UTF-8 "không phải BOM" mà không gặp sự cố
flipSTAR
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.