Konvertieren Sie UTF-8 mit Stückliste in UTF-8 ohne Stückliste in Python

82

Zwei Fragen hier. Ich habe eine Reihe von Dateien, die normalerweise UTF-8 mit Stückliste sind. Ich möchte sie (idealerweise vorhanden) ohne Stückliste in UTF-8 konvertieren. Es scheint, als codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors)würde man damit umgehen. Aber ich sehe keine wirklich guten Beispiele für die Verwendung. Wäre dies der beste Weg, um damit umzugehen?

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

Es wäre auch ideal, wenn wir unterschiedliche Eingabecodierungen ohne explizites Wissen handhaben könnten (siehe ASCII und UTF-16). Es scheint, dass dies alles machbar sein sollte. Gibt es eine Lösung, die jede bekannte Python-Codierung und Ausgabe als UTF-8 ohne Stückliste verwenden kann?

1 vorgeschlagenen Sol'n von unten bearbeiten (danke!)

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)

Dies gibt mir den folgenden Fehler:

IOError: [Errno 9] Bad file descriptor

Kurznachricht

In Kommentaren wird mir gesagt, dass der Fehler darin besteht, dass ich die Datei mit dem Modus 'rw' anstelle von 'r +' / 'r + b' öffne, sodass ich meine Frage eventuell erneut bearbeiten und den gelösten Teil entfernen sollte.

Timpone
quelle
2
Sie müssen Ihre Datei zum Lesen und Aktualisieren öffnen, dh mit einem r+Modus. Fügen Sie bauch hinzu, damit es auch unter Windows funktioniert, ohne dass ein lustiges Line-Ending-Geschäft zustande kommt. Schließlich möchten Sie zum Anfang der Datei zurückkehren und sie am Ende abschneiden - siehe meine aktualisierte Antwort.
Martin Geisler

Antworten:

123

Verwenden Sie einfach den Codec "utf-8-sig" :

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

Das gibt Ihnen eine unicodeZeichenfolge ohne Stückliste. Sie können dann verwenden

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

um einen normalen UTF-8-codierten String wieder zu erhalten s. Wenn Ihre Dateien groß sind, sollten Sie vermeiden, sie alle in den Speicher einzulesen. Die Stückliste besteht einfach aus drei Bytes am Anfang der Datei, sodass Sie diesen Code verwenden können, um sie aus der Datei zu entfernen:

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

Es öffnet die Datei, liest einen Block und schreibt ihn 3 Byte früher als dort in die Datei. Die Datei wird direkt neu geschrieben. Eine einfachere Lösung besteht darin, die kürzere Datei wie die Antwort von newtover in eine neue Datei zu schreiben . Das wäre einfacher, würde aber für kurze Zeit den doppelten Speicherplatz belegen.

Wenn Sie die Codierung erraten möchten, können Sie die Codierung einfach von den meisten bis zu den am wenigsten spezifischen durchlaufen:

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

Eine UTF-16-codierte Datei wird nicht als UTF-8 dekodiert, daher versuchen wir es zuerst mit UTF-8. Wenn dies fehlschlägt, versuchen wir es mit UTF-16. Schließlich verwenden wir Latin-1 - dies funktioniert immer, da alle 256 Bytes zulässige Werte in Latin-1 sind. NoneIn diesem Fall möchten Sie möglicherweise stattdessen zurückkehren, da dies wirklich ein Fallback ist und Ihr Code dies möglicherweise sorgfältiger behandeln möchte (wenn dies möglich ist).

Martin Geisler
quelle
hmm, ich habe die Frage in Bearbeitung Nr. 1 mit Beispielcode aktualisiert, aber einen schlechten Dateideskriptor erhalten. Danke für jede Hilfe. Ich versuche das herauszufinden.
Timpone
63

In Python 3 ist es ganz einfach: Lesen Sie die Datei und schreiben Sie sie mit utf-8Codierung neu:

s = open(bom_file, mode='r', encoding='utf-8-sig').read()
open(bom_file, mode='w', encoding='utf-8').write(s)
Geng Jiawen
quelle
3
Beste Antwort im Web zu diesem Thema. Verwenden Sie einfach 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)
Newtover
quelle
Können Sie erklären, wie dieser Code funktioniert? $ remove_bom.py <input.txt> output.txt Habe ich recht?
Guneysos
@ Guneysus, ja, genau
Newtover
1
Ich habe gerade hinzugefügtheader = header[3:] if header[0:3] == codecs.BOM_UTF8 else header
Chinmayv
5

Dies ist meine Implementierung, um jede Art von Codierung ohne Stückliste in UTF-8 zu konvertieren und Windows-Enlines durch ein universelles Format zu ersetzen:

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
estevo
quelle
3

Ich habe diese Frage gefunden, weil ich Probleme configparser.ConfigParser().read(fp)beim Öffnen von Dateien mit dem UTF8-Stücklistenheader habe.

Wenn Sie nach einer Lösung suchen, um den Header zu entfernen, damit ConfigPhaser die Konfigurationsdatei öffnen kann, anstatt einen Fehler von: zu melden File contains no section headers, öffnen Sie die Datei wie folgt:

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

Dies kann Ihnen viel Aufwand ersparen, da das Entfernen des Stücklisten-Headers der Datei nicht erforderlich ist.

(Ich weiß, das klingt nicht verwandt, aber hoffentlich könnte dies Menschen helfen, die wie ich kämpfen.)

Alto.Clef
quelle
1
Da ich zum ersten Mal mit try gearbeitet habe - außer -> öffnet dies auch UTF-8 "nicht Stücklisten"
-codierte
2

Sie können Codecs verwenden.

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")
wcc526
quelle
überhaupt nicht verwendbares Snipplet (Dateihandle? auch Codecs.BOM_UTF8 geben einen Syntaxfehler zurück)
Max