python - bom - utf-16 vs utf-8
Convierta UTF-8 con BOM a UTF-8 sin BOM en Python (6)
Dos preguntas aquí. Tengo un conjunto de archivos que generalmente 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 buenos ejemplos de uso. ¿Esta sería 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 una codificación de entrada diferente sin conocer explícitamente (se ve ASCII y UTF-16). Parece que esto debería ser factible. ¿Hay alguna solución que pueda tomar cualquier codificación y salida de Python conocida como UTF-8 sin BOM?
edita 1 propuesta sol''n 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
Noticia de última hora
Me han dicho en comentarios que el error es abrir 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.
En Python 3 es bastante fácil: lea el archivo y vuelva a escribirlo con la codificación utf-8
:
s = open(bom_file, mode=''r'', encoding=''utf-8-sig'').read()
open(bom_file, mode=''w'', encoding=''utf-8'').write(s)
Encontré esta pregunta porque tenía problemas con configparser.ConfigParser().read(fp)
al abrir archivos con el encabezado BOM UTF8.
Para aquellos que están buscando 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 el siguiente:
configparser.ConfigParser().read(config_file_path, encoding="utf-8-sig")
Esto podría ahorrarle toneladas de esfuerzo al hacer que la eliminación del encabezado BOM del archivo sea innecesaria.
(Sé que esto no parece estar relacionado, pero espero que esto pueda ayudar a las personas que luchan como yo).
Esta es mi implementación para convertir cualquier tipo de codificación a UTF-8 sin BOM y reemplazar en línea de Windows 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
Puedes usar codecs.
import codecs
content = open("test.txt",''r'').read()
filehandle.close()
if content[:3] == codecs.BOM_UTF8
content = content[3:]
print content.decode("utf-8")
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 unicode
sin la lista de materiales. Puedes usar
s = u.encode("utf-8")
para obtener una cadena codificada UTF-8 normal en s
. Si sus archivos son grandes, entonces debe 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 que 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 use el doble de espacio en disco durante un período corto.
En cuanto a la adivinación de la codificación, puede recorrer la codificación de más a menos específico:
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 probamos con UTF-8. Si eso falla, entonces probamos con UTF-16. Finalmente, usamos Latin-1 - esto siempre funcionará ya que todos los 256 bytes son valores legales en Latin-1. Es posible que desee devolver None
en este caso, ya que es realmente una alternativa y su código podría querer manejar esto más cuidadosamente (si es que puede).
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)