BOM이있는 UTF-8을 Python에서 BOM이없는 UTF-8로 변환


82

여기에 두 가지 질문이 있습니다. 일반적으로 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로 출력 할 수있는 솔루션이 있습니까?

아래에서 제안 된 sol'n 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'모드로 파일을 여는 것이므로 결국 내 질문을 다시 편집하고 해결 된 부분을 제거해야합니다.


2
읽기 및 업데이트, 즉 r+모드 를 사용하려면 파일을 열어야합니다 . b재미있는 라인 엔딩 비즈니스 없이도 Windows에서도 작동하도록 추가하십시오 . 마지막으로 파일의 시작 부분으로 돌아가서 끝 부분을 자르고 싶을 것입니다. 업데이트 된 답변을 참조하십시오.
Martin Geisler

답변:


125

"utf-8-sig"코덱을 사용하면됩니다 .

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

unicodeBOM이없는 문자열을 제공합니다 . 그런 다음 사용할 수 있습니다.

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 's answer 과 같은 새 파일에 짧은 파일을 쓰는 입니다. 더 간단하지만 짧은 기간 동안 두 배의 디스크 공간을 사용합니다.

인코딩을 추측하려면 가장 구체적인 것부터 가장 적은 것까지 인코딩을 반복 할 수 있습니다.

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실제로 폴백이므로이 경우 대신 반환 할 수 있으며 코드에서이를 더 신중하게 처리 할 수 ​​있습니다 (가능한 경우).


흠, 편집 # 1의 질문을 샘플 코드로 업데이트했지만 잘못된 파일 설명자가 있습니다. 도움이 필요하면 thx. 이것을 알아 내려고 노력하고 있습니다.
timpone

64

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)

3
이 주제에 대한 웹 최고의 답변입니다. 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)

이 코드가 어떻게 작동하는지 설명해 주시겠습니까? $ remove_bom.py <input.txt> output.txt 맞나요?
guneysus

@guneysus, 예, 정확히
newtover

1
난 그냥 추가header = header[3:] if header[0:3] == codecs.BOM_UTF8 else header
chinmayv

5

이것은 BOM없이 모든 종류의 인코딩을 UTF-8로 변환하고 창 enlines를 범용 형식으로 대체하는 구현입니다.

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

3

configparser.ConfigParser().read(fp)UTF8 BOM 헤더로 파일을 열 때 문제가 발생했기 때문에이 질문을 찾았습니다 .

ConfigPhaser가 오류를보고하는 대신 구성 파일을 열 수 있도록 헤더를 제거하는 솔루션을 찾는 사람들을 File contains no section headers위해 다음과 같이 파일을여십시오.

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

이렇게하면 파일의 BOM 헤더를 불필요하게 제거하여 많은 노력을 절약 할 수 있습니다.

(나는 이것이 무관하게 들린다는 것을 알고 있지만 이것이 나처럼 고군분투하는 사람들을 도울 수 있기를 바랍니다.)


1
내가 처음으로 try로 작업하고 있었기 때문에-> 이것은 UTF-8 "not BOM"인코딩 파일을 문제없이 엽니 다
flipSTAR

2

코덱을 사용할 수 있습니다.

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

사용할 수없는 스 니플 트 (filehandle? 또한 codecs.BOM_UTF8이 구문 오류를 반환 함)
Max
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.