Einführung in Windows-Zeichenkodierungen - Mojibake im Zusammenspiel mit Linux

· · Windows, Mojibake, UTF-8, CP932, Linux, PowerShell, Unicode

Mojibake unter Windows entsteht nicht, weil Japanisch schwierig ist. Fast immer liegt die Ursache darin, dass dieselbe Bytefolge mit einer anderen Zeichenkodierung gelesen wurde, oder dass das Ergebnis eines solchen Fehllesens mit einer weiteren Kodierung gespeichert wurde.

Besonders wenn Windows und Linux aufeinandertreffen, bleiben auf der Windows-Seite mehrere Kontexte bestehen — CP932, UTF-8, UTF-16, die Codepage der Konsole, Versionsunterschiede in PowerShell —, während die Linux-Seite meist von UTF-8 ausgeht. Dadurch treten zuvor unsichtbare Annahmefehler schlagartig zutage.

Es geht hier weniger um die Schwierigkeit der Japanisch-Verarbeitung als darum, ob man sich darüber im Klaren ist, unter welcher Annahme man Bytes behandelt. Dieser Artikel ordnet die Zeichenkodierung unter Windows aus der Perspektive „Warum entsteht Mojibake” ein und fasst praxisnah die Punkte zusammen, an denen sich Unfälle häufen, sobald Linux ins Spiel kommt.

Zielgruppe sind Leserinnen und Leser, die unter Windows erstellte CSV-/Log-/Konfigurationsdateien an Linux weiterreichen — oder umgekehrt — und die Ursachenklärung sowie Wiederherstellung bei Mojibake selbst durchführen möchten. Kenntnisse einer bestimmten Sprache oder eines Frameworks werden nicht vorausgesetzt. Die Beispielbefehle verwenden iconv und PowerShell.

1. Was zuerst wichtig ist

Vorab nur die Kernpunkte: Wichtig sind die folgenden 6 Punkte.

  • Mojibake ist kein Problem der „Zeichen”, sondern ein Problem der „Interpretation der Bytefolge”.
  • Unter Windows koexistieren die Unicode-Welt und die Welt der Legacy-Codepages, und selbst auf einer einzigen Maschine unterscheiden sich die Annahmen je nach Kontext.
  • Auf der Linux-Seite herrscht eine starke UTF-8-Annahme, sodass es leicht zu Unfällen kommt, wenn sich windowsseitiges CP932 oder UTF-16 einmischt.
  • Die Stufe, in der nur die Anzeige verstümmelt ist, und die Stufe, in der bereits beschädigter Inhalt gespeichert wurde, sollten getrennt betrachtet werden.
  • Es ist sicher, bei neuem Text UTF-8 als erste Wahl zu setzen und bestehende Legacy-Dateien bis zu einer expliziten Migrationsaufgabe unverändert zu lassen.
  • Die Kodierung der Datei, die Kodierung des Editors, die Codepage der Konsole und das interne Zeichenfolgenformat der Anwendung sind verschiedene Dinge. Vermischt man sie, verläuft sich die Untersuchung.

Mit der Aussage „unter Windows kam es zu Mojibake” allein lässt sich die Ursache nicht bestimmen. Mindestens muss man trennen, welcher der folgenden Punkte nicht übereinstimmt.

  • Die Kodierung der Datei selbst
  • Die beim Speichern verwendete Kodierung
  • Die Interpretation des Editors
  • Die Input-/Output-Codepage der Konsole
  • Das interne Zeichenfolgenformat der Anwendung
  • Die Locale und angenommene Kodierung auf der Linux-Seite

1.1 Vorab: Begriffe

Abkürzungen, die im Text ohne weitere Erklärung vorkommen, fassen wir vorab kurz zusammen.

Begriff Vollständige Bezeichnung Bedeutung in diesem Artikel
BOM Byte Order Mark Ein paar Bytes am Dateianfang als Kennzeichen. Sie teilen dem Leser mit, um welche Unicode-Kodierung es sich handelt; bei UTF-16 zeigen sie zusätzlich die Byte-Reihenfolge an. Bei UTF-8 ist es optional
Codepage Der Mechanismus, mit dem Windows über eine Nummer festlegt, „mit welcher Legacy-Zeichenkodierung” interpretiert wird. CP932 des japanischen Windows ist eine dieser Nummern
ANSI Bezeichnung unter Windows für die jeweils „aktive Codepage”. Die tatsächliche Bedeutung hängt von der Umgebung ab; in einer japanischen Umgebung ist es CP932
Locale Eine Einstellung, die Sprache, Region und Standardkodierung bündelt. Unter Linux wird sie über LANG oder LC_ALL angegeben und schließt, wie bei ja_JP.UTF-8, auch die Kodierung ein
WSL Windows Subsystem for Linux Der Mechanismus, um Linux unter Windows laufen zu lassen. Da die Annahmen von Windows- und Linux-Seite auf einer Maschine zusammentreffen, ist dies ein Ort, an dem die in diesem Artikel behandelten Unfälle leicht auftreten
ETL Extract / Transform / Load Die Verarbeitung, Daten zu entnehmen, umzuwandeln und zurückzuschreiben. Da dabei Dateien zwischenzeitlich erneut gelesen und wieder gespeichert werden, ist dies eine Stelle, an der sich die Kodierung ändert

2. Was Mojibake tatsächlich ist

Was Mojibake tatsächlich ist, ist recht einfach.

  1. Eine Zeichenfolge wird mit einer Kodierung encodiert und dadurch zu einer Bytefolge
  2. Diese Bytefolge wird mit einer Kodierung decodiert und dadurch wieder zu einer Zeichenfolge
  3. Stimmen die Annahmen von Encode und Decode nicht überein, wird sie als andere Zeichenfolge gelesen

Speichert man zum Beispiel als UTF-8, ergibt sich folgende Bytefolge.

E3 81 82

Liest man diese Bytefolge als UTF-8, ergibt sich ; liest man sie im Kontext von CP932, sieht man eine andere Zeichenfolge wie 縺�. Das ist Mojibake.

Wichtig ist: Hier ist nicht „das Japanische kaputtgegangen”, sondern lediglich die Interpretation derselben Bytes ist auseinandergelaufen.

Sehen wir uns auch die umgekehrte Richtung an. Speichert man als CP932, ergibt sich folgende Bytefolge.

82 A0

Versucht man, diese Bytefolge als UTF-8 zu lesen, sind weder 0x82 noch 0xA0 als gültiges UTF-8-Startbyte zulässig, sodass beide zu Ersatzzeichen werden und �� erscheint. Aus UTF-8-Sicht ist das von vornherein gar kein gültiges Zeichen.

Ein etwas längeres Beispiel zeigt die Eigenheit deutlicher. Speichert man 日本語 als CP932, ergibt sich folgende Bytefolge.

93 FA 96 7B 8C EA

Liest man das als UTF-8, ergibt sich etwa ���{��. Beachtenswert ist hier, dass allein das vierte Byte, 0x7B, unverändert als ASCII-Zeichen { durchrutscht. Da das zweite Byte von CP932 mitunter im ASCII-Bereich liegt, mischen sich in das verstümmelte Ergebnis Zeichen wie { oder \.

Das bedeutet: Je nach Richtung unterscheidet sich das Symptom.

Tatsächliche Bytefolge Annahme des Lesers Erscheinungsbild
UTF-8 CP932 Es erscheinen halbwegs plausibel wirkende Kanji oder Katakana wie
CP932 UTF-8 Überwiegend Ersatzzeichen , gelegentlich mit ASCII-Symbolen wie { durchmischt

Man kann grob abschätzen: „Stehen unlesbare Kanji da, wird UTF-8 als CP932 gelesen.” „Sind es überwiegend Ersatzzeichen, wird CP932 als UTF-8 gelesen.” Diese Asymmetrie zu kennen, beschleunigt die Ursachenklärung.

2.1 Ist nur die Anzeige verstümmelt, lässt sich manches noch retten

Mojibake hat eine Stufe, auf der noch Rettung möglich ist. Sind zum Beispiel die ursprünglichen Bytes unverändert, lässt sich die Datei durch erneutes Öffnen mit der richtigen Kodierung mitunter wiederherstellen.

Gefährlich ist umgekehrt ein Ablauf wie dieser.

  1. Eine UTF-8-Datei wird fälschlich als CP932 gelesen
  2. Auf dem Bildschirm erscheint etwas wie 縺�
  3. Die „so angezeigte Zeichenfolge” wird unverändert gespeichert
  4. Die ursprünglichen UTF-8-Bytes gehen verloren

Ist dieser Punkt erst erreicht, handelt es sich nicht mehr um einen bloßen Anzeigefehler, sondern um Datenverlust.

2.2 Noch gefährlicher: „nicht darstellbare Zeichen” in eine schmale Codepage zwingen

Ein weiterer klassischer Unfall entsteht, wenn eine Unicode-Zeichenfolge in eine Legacy-Codepage wie CP932 heruntergebrochen wird.

Enthält die Zeichenfolge zum Beispiel Zeichen, die in der Codepage der Gegenseite nicht existieren, geschieht Folgendes:

  • Ersetzung durch ?
  • Einfügen des Ersatzzeichens
  • Umwandlung in ein ähnliches, aber anderes Zeichen
  • Fehlschlag der Umwandlung

Diesen Unfall sollte man nicht nur an lesbar oder unlesbar messen, sondern daran, ob eine Hin- und Rückumwandlung wieder das Original ergibt. Ein einmal verlorenes Zeichen lässt sich auch mit späterer Kenntnis der richtigen Kodierung nicht wiederherstellen.

3. Warum es unter Windows besonders unübersichtlich wird

Windows ist nicht deshalb unübersichtlich, weil es einfach alt ist. Es liegt daran, dass die Welt von Unicode und die Welt der Legacy-Codepages bis heute nebeneinander existieren.

3.1 In der Windows-API koexistieren Unicode- und Codepage-Linien

Die Windows-API hat im Wesentlichen zwei Linien.

  • Die W-Familie: wide character. Behandelt Unicode als UTF-16
  • Die A-Familie: die sogenannte ANSI-Codepage-Linie

Das heißt, in Windows gibt es von Anfang an sowohl einen Weg „als Unicode behandeln” als auch einen Weg „über die jeweils aktive Codepage behandeln”. Deshalb unterscheiden sich die Annahmen selbst auf demselben Windows-System danach, welche API oder welches Tool durchlaufen wurde.

3.2 „Japanisch unter Windows” ist nicht eine einzige Sache

Im Umgang mit japanischem Text unter Windows vermischen sich in der Praxis häufig diese vier:

  • CP932: kommt oft bei Legacy-Text von japanischem Windows vor
  • UTF-8: nimmt bei neueren Textbeständen, im Web und in plattformübergreifenden Kontexten zu
  • UTF-16LE: taucht auch heute noch ganz selbstverständlich im Kontext von Windows-Tools und -APIs auf
  • Die Codepage der Konsole: eine eigene Ebene, die Ein-/Ausgabe von cmd.exe und einigen Konsolentools betrifft

Wichtig ist hier: Nicht weil man chcp 65001 ausgeführt hat, wird eine Datei zu UTF-8. Die Codepage der Konsole zu ändern und was die Bytes einer bestehenden Datei tatsächlich sind, sind zwei getrennte Fragen.

Übrigens wird der Legacy-Text von japanischem Windows oft locker „Shift_JIS” genannt, aber in der Praxis verhindert der Name CP932 eher, dass das Gespräch abdriftet. Zumindest macht er klar, dass „von der windows-eigenen japanischen Legacy-Kodierung die Rede ist”.

3.3 Dateiname und Dateiinhalt sind getrennte Fragen

Wenn unter Windows ein japanischer Dateiname ganz normal angezeigt wird, neigt man zu dem Gedanken „dann wird auch der Inhalt in Ordnung sein”. Genau hier lauert die Gefahr.

  • Die Ebene, die Pfad / Dateiname behandelt
  • Die Ebene, die den Dateiinhalt liest
  • Die Ebene, die in der Konsole anzeigt

Diese drei sind getrennt.

Ein japanischer Pfad lässt sich zum Beispiel problemlos verarbeiten, während der Dateiinhalt in CP932 gespeichert ist und auf der Linux-Seite als UTF-8 gelesen kaputtgeht. Umgekehrt kann der Dateiinhalt UTF-8 sein, während allein die Anzeige verstümmelt ist, weil die Codepage der Konsole nicht passt.

Als Bild sieht die Schichtbeziehung so aus.

SchreiberApp / Editor / SkriptBytefolge der Dateinur dies ist TatsacheLeser A: Editorautomatische Erkennung oder angegebene KodierungLeser B: KonsoleEin-/Ausgabe-CodepageLeser C: AnwendungsinternStandardkodierung der BibliothekLeser D: Linux-SeiteUTF-8-Annahme gemäß Localenur die Anzeige verstümmeltwird beim erneuten Speichern zu Datenverlustnur die Anzeige verstümmeltDatei bleibt unversehrtVerarbeitungsergebnis kaputtpflanzt sich stromabwärts fortDecode-Fehler oder Ersatzzeichen

Zwei Punkte sind entscheidend. Erstens: unterscheiden, ob die Bytefolge in der Mitte kaputt ist, oder der Leser rechts. Zweitens: Die vier rechten Punkte sind voneinander unabhängig, sodass eine Bestätigung bei einem Punkt keine Garantie für die anderen drei ist. Dass „in der Konsole lesbar, also auch im Editor in Ordnung” nicht gilt, liegt genau an dieser Struktur.

3.4 Auch die Standardwerte von PowerShell und Begleitwerkzeugen sind nicht aufeinander abgestimmt

Ein unauffälliger Unfallverstärker unter Windows ist, dass dasselbe „ich habe Text geschrieben” je nach Weg unterschiedliche Ausgabe-Bytes erzeugt.

Besonders zu beachten ist Folgendes:

  • Windows PowerShell 5.1 hat keine durchgängig konsistente Standardkodierung
  • Manche Cmdlets und Umleitungen erzeugen UTF-16LE
  • Auf anderen Wegen wird die aktive ANSI-Codepage verwendet
  • PowerShell 7 und neuer verwendet standardmäßig UTF-8 ohne BOM

Das heißt: Allein aus „mit PowerShell erzeugter Text” lässt sich die Kodierung nicht bestimmen. Man muss wissen, mit welcher Version, welchem Cmdlet und welchem Schreibweg gearbeitet wurde.

Welcher Weg welche Bytes erzeugt, ist im Microsoft-Learn-Artikel about_Character_Encoding zusammengestellt. Die gängigsten davon:

Schreibweg Standard in Windows PowerShell 5.1 Standard in PowerShell 7
Out-File, >, >> UTF-16LE (mit BOM) UTF-8 ohne BOM
Set-Content, Add-Content (neu oder leere Datei) ANSI = aktive Codepage. In japanischer Umgebung CP932 UTF-8 ohne BOM
Export-Csv ASCII. Nicht-ASCII-Zeichen entfallen UTF-8 ohne BOM
Export-Clixml, New-ModuleManifest UTF-16LE UTF-8 ohne BOM
New-Item -Type File -Value UTF-8 ohne BOM UTF-8 ohne BOM
Start-Transcript UTF-8 mit BOM UTF-8 ohne BOM

Auch beim Lesen gibt es Unterschiede. Beim Lesen einer Datei ohne BOM geht Get-Content unter 5.1 von ANSI aus, während Import-Csv und Select-String von UTF-8 ausgehen. Innerhalb derselben Sitzung sind die Annahmen also unterschiedlich.

Praktisch am stärksten schmerzt hier, dass bei demselben „Text geschrieben” Out-File UTF-16LE, Set-Content dagegen CP932 erzeugt. Das in 4.3 genannte „binär wirkende Text mit massenhaft NUL-Bytes” stammt meist aus dem Standardverhalten von > oder Out-File.

Ein weiterer Punkt: Auch mit -Encoding UTF8 erzeugt 5.1 eine Datei mit BOM. Möchte man unter 5.1 UTF-8 ohne BOM schreiben, muss man auf .NET zurückgreifen.

# UTF-8 ohne BOM unter Windows PowerShell 5.1 schreiben
$text = "日本語を含む本文"
[System.IO.File]::WriteAllText(
    "C:\work\output.txt", $text,
    (New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false))

Dass das Argument von UTF8Encoding auf $false gesetzt wird, ist die Angabe, kein BOM anzuhängen. Unter PowerShell 7 erzielt -Encoding utf8NoBOM dasselbe Ergebnis.

4. Typische Unfälle im Zusammenspiel mit Linux

Was unter Windows allein irgendwie funktionierte, bricht nicht selten in dem Moment zusammen, in dem Linux ins Spiel kommt. Der Grund ist einfach: Auf der Linux-Seite herrscht eine starke UTF-8-Annahme.

4.1 Windows speichert Text als CP932, Linux liest ihn als UTF-8

Der mit Abstand häufigste Unfall.

  • Eine Legacy-App unter Windows oder ein alter Betriebsablauf schreibt CSV / TXT / Log in CP932
  • Skripte und Tools auf der Linux-Seite lesen gemäß Locale mit UTF-8-Annahme
  • Das Ergebnis: Decode-Fehler, , oder unverständliche Zeichenfolgen

Hier trägt nicht das Linux-Tool die Schuld — die eigentliche Ursache ist, dass den empfangenen Bytes keine Absprache über ihre Kodierung mitgegeben wurde.

4.2 Windows behandelt unter Linux / VS Code erzeugtes UTF-8 ohne BOM als ANSI

Auch die umgekehrte Richtung kommt vor.

  • Unter Linux oder in VS Code wird ein Skript / eine Konfiguration / eine Textdatei als UTF-8 ohne BOM erstellt
  • Windows PowerShell 5.1 oder ein Legacy-Tool behandelt die BOM-lose Datei als Codepage auf der ANSI-Seite
  • Nur die Zeilen mit japanischem oder anderem Non-ASCII-Text gehen kaputt

Hier wird oft UTF-8 die Schuld gegeben, tatsächlich liegt die Ursache aber darin, dass sich ein Leser, der BOM-loses UTF-8 nicht korrekt erkennt, in die Kette mischt.

4.3 Windows schreibt UTF-16LE, und auf der Linux-Seite „sieht es nicht wie Text aus”

Auch das kommt recht häufig vor.

  • Manche Ausgaben von Windows PowerShell 5.1 oder ein Legacy-Tool schreiben UTF-16LE
  • Text-Tools auf der Linux-Seite erwarten einen UTF-8-1-Byte-Stream
  • Das Ergebnis ist „binär wirkender Text” mit massenhaft eingemischten NUL-Bytes

UTF-16LE an sich ist nicht schlecht. Es passt aber oft nicht zur Annahme, es direkt in Linux-Textverarbeitungs-Tools weiterzuleiten.

4.4 Auch die BOM-Anwesenheit verursacht Reibung

Das BOM ist nicht die Kodierung selbst, wirkt sich in der Praxis aber deutlich aus.

  • Manchen windowsseitigen Tools hilft ein BOM
  • Manche linuxseitigen Tools behandeln das BOM als überflüssige führende Bytes
  • Das Ergebnis: nur die erste Spalte oder der Anfang der ersten Zeile geht kaputt, unsichtbarer Müll erscheint, Vergleichsergebnisse verschieben sich

Besonders bei UTF-8 gilt: UTF-8 mit BOM und UTF-8 ohne BOM sind unterschiedliche Bytes. Mit „wir haben auf UTF-8 umgestellt” ist eine Betriebsregel erst zur Hälfte festgelegt.

4.5 Sich auf das Erscheinungsbild der Konsole zu verlassen führt in die Irre

Beim Übergang zwischen Windows und Linux ist noch etwas anderes gefährlich: die Konsole.

  • Die Windows-Konsole besitzt Input-/Output-Codepages
  • Linux-Terminals laufen meist unter der Annahme einer UTF-8-Locale
  • Über WSL, SSH, Container und CI vervielfachen sich die Anzeigewege

Urteilt man in diesem Zustand „in der Konsole lesbar, also ist die Datei in Ordnung” oder „in der Konsole verstümmelt, also ist die Datei kaputt”, liegt man leicht daneben. Ob das Sichtbare kaputt ist und ob die gespeicherten Bytes kaputt sind, sollte man getrennt prüfen.

4.6 Die typischen Unfälle in Tabellenform

Situation Tatsächliche Bytes Annahme des Lesers Typisches Symptom
Von einer Legacy-Windows-App gespeicherte CSV CP932 Linux-Seite nimmt UTF-8 an , Decode-Fehler, unverständliches Japanisch
Unter Linux / VS Code erstellte Datei UTF-8 ohne BOM Windows PowerShell 5.1 behandelt sie als ANSI Nur die japanischen Zeilen gehen kaputt
Manche Ausgabe von Windows PowerShell 5.1 UTF-16LE oder ANSI Linux-Seite erwartet UTF-8-Text NUL-Bytes mischen sich ein, binärähnliches Verhalten
UTF-8-Datei mit BOM UTF-8 + BOM Unix-Tools nehmen reines UTF-8 an Nur die erste Spalte kaputt, überflüssige Zeichen
Man vertraut allein der Konsolenanzeige Datei und Konsole folgen unterschiedlichen Annahmen Untersuchende Person urteilt allein nach der Anzeige Die Ursachenklärung geht fehl

5. Die Mojibake-Untersuchung mit diesen 4 Fragen vorantreiben

Hängt die Mojibake-Untersuchung fest, kommt man am schnellsten voran, wenn man zu diesen 4 Fragen zurückkehrt.

5.1 Was sind die ursprünglichen Bytes?

Zuerst sollte man betrachten, „welche Bytes diese Datei jetzt tatsächlich hat”. Man braucht die Einstellung, Bytes zu betrachten, nicht das Erscheinungsbild.

  • Ist es UTF-8?
  • UTF-8 mit BOM?
  • CP932?
  • UTF-16LE?
  • Wurde die Datei zwischenzeitlich erneut gespeichert und dadurch zu etwas anderem?

5.2 Wer hat zuerst geschrieben, unter welcher Annahme?

Als Nächstes wird „der ursprüngliche Schreiber” bestimmt.

  • Eine Legacy-App unter Windows?
  • PowerShell 5.1 oder 7?
  • Ein Linux-Skript?
  • VS Code?
  • Ein Export aus Excel?
  • Irgendeine Middleware / ein Batch / eine CI?

Bleibt das unklar, wird die Kodierungsbestimmung zum Glücksspiel.

5.3 Wer liest jetzt, unter welcher Annahme?

Neben dem Schreiber braucht man auch die Annahme des Lesers.

  • Erkennt der Editor automatisch?
  • Achtet PowerShell auf das BOM?
  • Behandelt die Linux-Seite die Datei gemäß Locale als UTF-8?
  • Verwendet die Bibliothek ihre Standardkodierung?
  • Wird Encoding.UTF8 oder cp932 explizit angegeben?

Mojibake entsteht fast immer genau hier.

5.4 Wurde der fehlgelesene Inhalt bereits gespeichert?

Zuletzt prüft man, ob der Schaden bei der Anzeige stehen geblieben ist.

  • Sind die Bytes noch die ursprünglichen?
  • Hat jemand den verstümmelt aussehenden Inhalt gespeichert?
  • Sind ? oder in den Diff eingegangen?
  • Wurde der gesamte Text mit einer anderen Kodierung neu geschrieben?

Sind diese 4 Fragen beantwortet, ist die Ursache meist sichtbar.

6. Wie man eine beschädigte Datei repariert

Ist die Ursache geklärt, folgt die Wiederherstellung. Zuerst entscheidet sich hier, ob die ursprünglichen Bytes noch vorhanden sind.

  • Die ursprünglichen Bytes sind noch vorhanden: Mit der richtigen Kodierung erneut lesen und mit der gewünschten Kodierung wieder ausgeben — dann lässt sich die Datei zurückholen. Das ist das Vorgehen dieses Kapitels
  • Der fehlgelesene Inhalt wurde bereits gespeichert: Es bleibt nur die Wiederherstellung aus einem Backup oder der Git-Historie. Zu Ersatzzeichen oder ? gewordene Zeichen lassen sich auch bei Kenntnis der richtigen Kodierung nicht wiederherstellen

Deshalb sollte man als Allererstes eine Kopie des Arbeitsobjekts anlegen. Die Umwandlung erfolgt an der Kopie; die Originaldatei bleibt unangetastet.

6.1 Auf der Linux-Seite: iconv

Um eine CP932-Datei nach UTF-8 zu bringen, ist iconv die naheliegendste Wahl.

# CP932 -> UTF-8
iconv -f CP932 -t UTF-8 input.csv > output.csv

Ist die Quellkodierung falsch angegeben, bricht die Umwandlung mittendrin so ab:

iconv: illegal input sequence at position 0

Dass der Vorgang abbricht, ist selbst schon eine Information: „Das ist nicht diese Kodierung.” Es lohnt sich also, verschiedene Kandidaten durchzuprobieren. Bricht die Umwandlung dagegen bei keiner Angabe ab, besteht die Möglichkeit, dass die Datei nur aus ASCII besteht.

Beim Umwandeln von UTF-16LE nach UTF-8 verwendet man -f UTF-16LE. Gibt man bei einer Datei mit BOM jedoch ausdrücklich -f UTF-16LE an, kann das BOM als das eine Zeichen U+FEFF in der Ausgabe verbleiben. Möchte man den Umgang mit dem BOM iconv selbst überlassen, verwendet man -f UTF-16 und prüft nach der Umwandlung unbedingt das erste Zeichen.

6.2 Auf der Windows-Seite: PowerShell

Ab PowerShell 6.2 lässt sich -Encoding direkt eine Codepage-Nummer übergeben. CP932 ist 932.

# PowerShell 6.2 oder neuer. CP932 -> UTF-8 ohne BOM
Get-Content -Path .\input.csv -Encoding 932 |
    Set-Content -Path .\output.csv -Encoding utf8NoBOM

Umgekehrt, wenn eine unter Linux erzeugte UTF-8-Datei an eine Gegenseite weitergegeben werden soll, die nur CP932 lesen kann:

Get-Content -Path .\input.csv -Encoding utf8 |
    Set-Content -Path .\output.csv -Encoding 932

Diese Schreibweise hat allerdings zwei Nebenwirkungen.

  1. Get-Content teilt zeilenweise auf, und Set-Content fügt hinter jeder Zeile wieder einen Zeilenumbruch ein. Das heißt, die Zeilenumbrüche werden vereinheitlicht
  2. Fehlt am Ende der Originaldatei ein Zeilenumbruch, erscheint in der Ausgabe trotzdem einer

Sollen die Bytes einschließlich der Zeilenumbrüche unverändert erhalten bleiben, behandelt man die Datei als Ganzes, ohne sie in Zeilen aufzuteilen.

# CP932 -> UTF-8 ohne BOM, Zeilenumbrüche unverändert erhalten
$text = [System.IO.File]::ReadAllText(
    "C:\work\input.csv", [System.Text.Encoding]::GetEncoding(932))
[System.IO.File]::WriteAllText(
    "C:\work\output.csv", $text,
    (New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false))

GetEncoding(932) lässt sich unter Windows PowerShell 5.1 direkt verwenden. Wirft dies unter PowerShell 7 in Ihrer Umgebung eine Ausnahme, führen Sie vorher einmal Folgendes aus.

[System.Text.Encoding]::RegisterProvider(
    [System.Text.CodePagesEncodingProvider]::Instance)

Ob ein BOM angehängt wird, legt man wie in 7.1 nach den Bedürfnissen der Gegenseite fest. Setzt man im obigen Beispiel das Argument von UTF8Encoding auf $true, entsteht eine Datei mit BOM.

6.3 Was man nach der Umwandlung unbedingt prüfen sollte

Die Umwandlung ist nicht schon damit erledigt, dass „kein Fehler aufgetreten ist”. Mindestens sollte man Folgendes prüfen.

  • Lassen sich 2–3 repräsentative japanische Zeilen korrekt lesen?
  • Haben ? oder zugenommen? Ist das der Fall, ist dieses Zeichen bereits verloren
  • Hat sich die Zeilenzahl verändert?
  • Entsprechen BOM und Zeilenumbruch der Erwartung der Gegenseite?
  • Hat sich die Dateigröße extrem verändert? Beim Wechsel von CP932 zu UTF-8 wächst der japanische Anteil von 2 auf 3 Bytes, sodass eine leichte Größenzunahme normal ist

Besonders der zweite Punkt ist wichtig. Bricht man UTF-8 mit Zeichen, die in CP932 nicht existieren, auf CP932 herunter, geht zwangsläufig Information verloren. Prüfen Sie wie in 2.2 beschrieben, ob eine Hin- und Rückumwandlung wieder das Original ergibt.

6.4 Massenumwandlung als eigene „Migrationsaufgabe” abtrennen

Zuletzt noch zum Betrieb. Eine einzelne beschädigte Datei zu reparieren und ein gesamtes Repository von CP932 auf UTF-8 umzustellen, sind unterschiedliche Aufgaben. Zweiteres sollten Sie, wie in 7.2 dieses Artikels beschrieben, nicht beiläufig im Rahmen einer normalen Funktionsänderung erledigen, sondern als eigenständige Migrationsaufgabe planen.

7. Betriebsregeln zur Unfallvermeidung

Von hier an geht es um die Praxis. Bei Projekten, die Windows und Linux verbinden, senkt es die Unfallquote deutlich, die folgenden Regeln vorab festzulegen.

7.1 Neue Dateien standardmäßig auf UTF-8 setzen

Für neue Textdateien ist es unbedenklich, zunächst UTF-8 als erste Wahl zu setzen. Hier darf man aber nicht stehen bleiben. Auch der Umgang mit dem BOM muss mitentschieden werden.

Empfehlenswert ist diese Festlegung:

  • Text, der überwiegend auf der Linux-Seite gelesen wird: standardmäßig UTF-8 ohne BOM
  • Skripte, die von Legacy-Tools unter Windows oder Windows PowerShell 5.1 gelesen werden: BOM-Vorhandensein explizit nach den Bedürfnissen der Gegenseite festlegen
  • Gibt es eine klare Gegenseite, die UTF-16LE benötigt, diese Anforderung als Vorgabe schriftlich festhalten

Schreibt man nur „vereinheitlicht auf UTF-8”, gibt es später Streit ums BOM.

7.2 Bestehende Legacy-Dateien bis zur expliziten Migrationsaufgabe unverändert lassen

Ist eine bestehende Datei in CP932, ist es sicherer, sie nicht beiläufig im Rahmen einer normalen Funktionsänderung nach UTF-8 umzustellen.

Betrieblich ist diese Form sicher:

  • Bestehende Dateien behalten ihre ursprüngliche Kodierung / ihr BOM / ihre Zeilenumbrüche
  • Kodierungsänderungen werden als eigene Migrationsaufgabe abgetrennt
  • Erst nach Prüfung von Umwandlungsziel, Auswirkungsbereich und nachgelagerten Konsumenten erfolgt die Massenumwandlung

Viele Mojibake-Unfälle beginnen mit einem wohlmeinenden „mal eben UTF-8 umstellen”.

7.3 Kodierung als Teil der Schnittstelle behandeln

Bei CSV, TXT, Log, Konfigurationsdateien und einfachen Protokollen ist nicht nur der Inhalt, sondern die Kodierung selbst Teil der Schnittstelle.

Als Vorgabe sollte mindestens Folgendes festgehalten werden:

  • Ist diese Datei UTF-8 / CP932 / UTF-16LE?
  • Trägt sie im Fall von UTF-8 ein BOM?
  • Sind die Zeilenumbrüche LF oder CRLF?
  • Wer ist Producer / Consumer, Linux oder Windows?
  • Speichert ein zwischengeschalteter Batch oder ETL sie erneut?

„Wir übergeben es als Text” ist keine Spezifikation.

7.4 Standardwerten nicht vertrauen, beim Schreiben explizit angeben

Sowohl im Code als auch im Skript ist es sicherer, die Kodierung explizit anzugeben.

Gefährlich sind Gedankengänge wie diese:

  • Mit dem Standard speichern
  • Wird schon irgendwie zum Betriebssystem passen
  • War in der Konsole lesbar, also wird die Datei schon in Ordnung sein
  • Es gibt ja automatische Erkennung, also wird es schon passen

Standardwerte unterscheiden sich ganz gewöhnlich zwischen Windows / Linux, PowerShell 5.1 / 7, Editor und Laufzeitumgebung. Ohne explizite Angabe funktioniert es leicht nur zufällig.

7.5 Konsole und Datei getrennt prüfen

Eine unauffällig, aber sehr wirksame Regel ist diese.

  • Prüfung der Anzeige in der Konsole
  • Prüfung durch erneutes Öffnen der Datei

Diese beiden trennt man.

Auch wenn chcp oder die Terminalanzeige stimmen, bedeutet das nichts, wenn die gespeicherte Datei eine andere Kodierung hat. Umgekehrt kann die Datei einwandfrei sein, während nur die Anzeige verstümmelt ist, weil die Anzeige-Codepage der Konsole nicht passt.

7.6 Git repariert die Kodierung nicht

Unauffällig, aber wichtig.

Git verfolgt grundsätzlich nur Bytes. Das heißt, auch kaputte Bytes werden gewissenhaft genauso in die Historie übernommen.

Tritt daher einer dieser Fälle auf,

  • ein riesiger Diff erscheint, obwohl nichts geändert wurde
  • nur die japanischen Zeilen zeigen rätselhafte Diffs
  • nur die erste Zeile hat sich geändert
  • Zeilenumbruch und Kodierung ändern sich gemeinsam

sollte man eher einen Re-Encoding-Unfall vermuten als eine inhaltliche Änderung.

8. Die Mindest-Checkliste

Hier eine Checkliste, die es sich lohnt, bei Projekten mit Windows-Linux-Mischbetrieb von Anfang an festzulegen.

8.1 Vor der Bearbeitung

  • Welche Kodierung hat diese Datei aktuell?
  • Gibt es ein BOM?
  • Sind die Zeilenumbrüche LF oder CRLF?
  • Wurden 2–3 repräsentative japanische Zeilen notiert?
  • Ist bekannt, ob Linux- oder Windows-Seite der endgültige Konsument ist?

8.2 Während der Bearbeitung

  • Wird beim Schreiben von der Standardkodierung abhängig gearbeitet?
  • Wird dem automatischen Erkennen beim Speichern vertraut?
  • Werden PowerShell- oder Shell-Umleitungswege unbedacht genutzt?
  • Beruhigt man sich nur damit, dass „die Anzeige lesbar ist”?

8.3 Nach der Bearbeitung

  • Wurde nach dem Speichern erneut geöffnet und geprüft?
  • Sind repräsentative Zeilen sowohl auf der Linux- als auch auf der Windows-Seite unverändert?
  • Haben ? oder im Diff zugenommen?
  • Ist nur die erste Zeile oder erste Spalte kaputt?
  • Ist der Diff ein reiner BOM- oder Zeilenumbruch-Unterschied ohne inhaltliche Änderung?

8.4 Was als Migrationsaufgabe gehört

  • Massenumwandlung von CP932 nach UTF-8
  • Vereinheitlichung der UTF-8-BOM-Policy
  • Bestandsaufnahme der Skripte, die von PowerShell 5.1 ausgehen
  • Dokumentation der Textwege über CI / Container / WSL / SSH
  • Vereinheitlichung der Speichereinstellungen von Editor / Formatter / Batch

9. Zusammenfassung

Fasst man das Windows-Kodierungsproblem in einem Satz zusammen, liegt sein Kern darin, dass die Welt von Unicode und die Welt der Legacy-Codepages bis heute nebeneinander bestehen.

Und dass sich Unfälle im Zusammenspiel mit Linux häufen, liegt daran, dass die Linux-Seite meist von UTF-8 ausgeht, sodass windowsseitiges CP932 und UTF-16, die Konsolen-Codepage und Versionsunterschiede in PowerShell schlagartig zutage treten.

Diese 5 Punkte sollte man sich merken:

  • Mojibake ist eine auseinanderlaufende Byte-Interpretation
  • Anzeigefehler und Datenverlust sind unterschiedliche Dinge
  • Unter Windows sollte man in getrennten Schichten denken: Datei / Editor / Konsole / API
  • Für Text im Austausch mit Linux sollte UTF-8 die erste Wahl sein
  • Die Umwandlung bestehender Legacy-Dateien sollte von der gewöhnlichen Wartung getrennt bleiben

Nimmt man „unter Windows kam es zu Mojibake” wörtlich, ist das Thema zu breit. Aber schneidet man es mit diesen

  • Was sind die ursprünglichen Bytes?
  • Wer hat wie geschrieben?
  • Wer hat wie gelesen?
  • Wurde bereits gespeichert?

4 Fragen zu, lässt sich das Thema gut ordnen.

Zeichenkodierung ist unauffällig, aber zwischen Windows und Linux ist sie der I/O-Vertrag selbst. Diesen Punkt nicht offenzulassen, ist die wirksamste Gegenmaßnahme.

10. Referenzen

Windows / Microsoft

PowerShell / VS Code

GNU / Linux-Locale

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Windows-App-Entwicklung

In Windows-Fachanwendungen sind CP932 und UTF-8 häufig gemischt im Einsatz; den Umgang mit Zeichenkodierung fest ins Design aufzunehmen, wirkt sich direkt auf die Wartbarkeit aus.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Wodurch entsteht Mojibake unter Windows?
Fast immer dadurch, dass dieselbe Bytefolge mit einer anderen Zeichenkodierung gelesen wurde, oder dass das Ergebnis eines solchen Fehllesens mit einer weiteren Kodierung gespeichert wurde. Speichert man etwa „あ“ als UTF-8 (Bytefolge E3 81 82) und liest diese Bytes im Kontext von CP932, erscheint eine andere Zeichenfolge wie „縺“. Es liegt nicht daran, dass Japanisch schwierig ist — die eigentliche Ursache ist, dass die Annahmen von Encode und Decode nicht übereinstimmen.
Lässt sich eine bereits verstümmelte Datei wieder reparieren?
Ist die ursprüngliche Bytefolge unverändert, lässt sie sich in manchen Fällen durch erneutes Öffnen mit der richtigen Kodierung wiederherstellen. Gefährlich wird es, wenn der fehlgelesene, verstümmelt aussehende Inhalt genau so gespeichert wird — ab diesem Punkt handelt es sich nicht mehr um einen Anzeigefehler, sondern um Datenverlust. Wird zudem eine Unicode-Zeichenfolge in eine schmale Codepage wie CP932 heruntergebrochen und dabei zu „?“ oder einem Ersatzzeichen, lässt sich das verlorene Zeichen später auch mit Kenntnis der richtigen Kodierung nicht mehr wiederherstellen.
Macht chcp 65001 auch Dateien zu UTF-8?
Nein. Die Codepage der Konsole zu ändern und was die Bytefolge einer bestehenden Datei tatsächlich ist, sind zwei getrennte Fragen. Unter Windows sind die Kodierung der Datei selbst, die Interpretation des Editors, die Input-/Output-Codepage der Konsole und das interne Zeichenfolgenformat der Anwendung jeweils eigenständige Dinge, die man getrennt betrachten muss. Der Schluss „in der Konsole lesbar, also ist auch die Datei in Ordnung“ trügt leicht — die Prüfung der Konsolenanzeige und die Prüfung durch erneutes Öffnen der Datei sollten getrennt erfolgen.
Welche sicheren Regeln gelten für den Textaustausch zwischen Windows und Linux?
Neue Dateien standardmäßig auf UTF-8 setzen und dabei auch den Umgang mit dem BOM festlegen. Text, der überwiegend auf der Linux-Seite gelesen wird, sollte grundsätzlich UTF-8 ohne BOM sein; liest ihn ein Legacy-Tool wie Windows PowerShell 5.1, wird das BOM explizit nach den Bedürfnissen der Gegenseite festgelegt. Bestehende CP932-Dateien sollten nicht beiläufig im Rahmen einer normalen Änderung umkodiert, sondern als eigene, ausdrückliche Migrationsaufgabe abgetrennt werden. Da bei CSV und Logs die Kodierung selbst Teil der Schnittstelle ist, senkt es die Unfallquote, Kodierung, BOM und Zeilenumbruch als Vorgabe schriftlich festzuhalten.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog