Convierta UTF-8 con BOM a UTF-8 sin BOM en Python

Dos preguntas aquí. Tengo un conjunto de archivos que normalmente son UTF-8 con BOM. Me gustaría convertirlos (idealmente en su lugar) a UTF-8 sin BOM. Parece que codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors) manejaría esto. Pero realmente no veo ningún buen ejemplo de uso. ¿Sería esta la mejor manera de manejar esto?

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

Además, sería ideal si pudiéramos manejar diferentes codificaciones de entrada sin el conocimiento explícito (ver ASCII y UTF-16). Parece que todo esto debería ser factible. ¿Hay alguna solución que pueda tomar cualquier encoding conocida de Python y generar como UTF-8 sin BOM?

Editar 1 sol’n propuesto desde abajo (gracias!)

 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) 

Esto me da el siguiente error:

 IOError: [Errno 9] Bad file descriptor 

Noticias de última hora

Me comentan en los comentarios que el error es que abro el archivo con el modo ‘rw’ en lugar de ‘r +’ / ‘r + b’, por lo que eventualmente debería volver a editar mi pregunta y eliminar la parte resuelta.

Simplemente use el códec “utf-8-sig” :

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

Eso le da una cadena de unicode sin la lista de materiales. A continuación, puede utilizar

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

para recuperar una cadena codificada en UTF-8 normal en s . Si tus archivos son grandes, debes evitar leerlos todos en la memoria. La lista de materiales es simplemente tres bytes al principio del archivo, por lo que puede usar este código para eliminarlos del archivo:

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

Abre el archivo, lee un fragmento y lo escribe en el archivo 3 bytes antes de donde lo leyó. El archivo se reescribe en el lugar. Como solución más fácil es escribir el archivo más corto en un nuevo archivo como la respuesta de newtover . Eso sería más simple, pero usa el doble de espacio en disco por un período corto.

En cuanto a adivinar la encoding, entonces puede hacer un ciclo a través de la encoding de la más específica a la menos específica:

 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 

Un archivo codificado en UTF-16 no se decodificará como UTF-8, por lo que primero intentamos con UTF-8. Si eso falla, entonces intentamos con UTF-16. Finalmente, usamos Latin-1, esto siempre funcionará ya que los 256 bytes son valores legales en Latin-1. Es posible que desee devolver None en su lugar en este caso, ya que es realmente un repliegue y su código podría querer manejar esto con más cuidado (si puede).

En Python 3 es bastante sencillo: lea el archivo y vuelva a escribirlo con la encoding 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) 

Esta es mi implementación para convertir cualquier tipo de encoding a UTF-8 sin BOM y reemplazar las ventanas en línea por formato universal:

 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 

Encontré esta pregunta porque tengo problemas con configparser.ConfigParser().read(fp) al abrir archivos con el encabezado BOM de UTF8.

Para aquellos que buscan una solución para eliminar el encabezado para que ConfigPhaser pueda abrir el archivo de configuración en lugar de informar un error de: El File contains no section headers , abra el archivo como se muestra a continuación:

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

Esto podría ahorrarle un montón de esfuerzo al hacer innecesaria la eliminación del encabezado de la lista de materiales del archivo.

(Sé que esto no parece estar relacionado, pero espero que esto pueda ayudar a las personas que luchan como yo).

Puedes usar codecs.

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