An Introduction to Windows Text Encodings - The Mojibake That Happens When Integrating with Linux

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

Revision history (2 updates, last updated Sep 1, 2026)

A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.

Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version.
Fixed a display problem where lines containing a vertical bar were rendered as a table, leaving the reference links unclickable. The text itself is unchanged.
First published
Cite this article(DOI: 10.5281/zenodo.21614561)

This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.

Go Komura (2026). An Introduction to Windows Text Encodings - The Mojibake That Happens When Integrating with Linux. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614561 https://comcomponent.com/en/blog/2026/03/21/000-windows-text-encoding-mojibake-linux/

DOI (latest version)
10.5281/zenodo.21614561
DOI (this version)
10.5281/zenodo.22218885

Mojibake on Windows does not happen because Japanese is difficult. Almost all of it is caused by reading the same byte sequence as a different encoding, or by saving the result of a misread in yet another encoding.

Crossing between Windows and Linux makes this especially visible. The Windows side still carries several contexts at once - CP932, UTF-8, UTF-16, the console code page, PowerShell version differences - while the Linux side mostly flows on a UTF-8 assumption, so mismatched assumptions that had been invisible until then surface all at once.

How assumptions diverge between Windows and LinuxA diagram showing that the Windows side keeps several contexts alive such as CP932, UTF-16, the console code page and PowerShell version differences, and that combining them with a Linux side that mostly runs on a UTF-8 assumption brings the mismatched assumptions to the surface.Windows sideCP932 / UTF-16 / code page / version differencesLinux sideStrong UTF-8 assumptionMismatched assumptions surface

Figure 1: Where the several contexts left on the Windows side meet the UTF-8 assumption of the Linux side, the mismatch surfaces all at once.

This is less about the difficulty of Japanese text processing and more about whether the assumptions under which bytes are handled have been lined up. This article organizes Windows text encodings from the angle of why mojibake happens, with a practical focus on the points where things break more often once Linux is part of the picture.

The intended reader is someone who hands CSVs, logs, or configuration files created on Windows over to Linux (or the other way around) and wants to isolate and repair mojibake without help. No specific language or framework knowledge is assumed. The command examples use iconv and PowerShell.

1. What to Grasp First

Stating the essentials up front, six points matter.

  • Mojibake is not a problem of characters. It is a problem of how a byte sequence was interpreted.
  • On Windows the Unicode world and the legacy code page world coexist, and even within a single machine the assumption differs by context.
  • The Linux side carries a strong UTF-8 assumption, so mixing in CP932 or UTF-16 from the Windows side breaks things easily.
  • The stage where only the display is garbled and the stage where the corrupted content has been saved should be thought about separately.
  • The safe default is to make UTF-8 the first choice for new text and to leave existing legacy files exactly as they are until an explicit migration task.
  • A file’s encoding, an editor’s encoding, the console code page, and an app’s internal string format are different things. Confuse them and the investigation goes in circles.

The phrase “it got garbled on Windows” is not enough to pin down a cause. At minimum you need to separate which of the following is out of line.

  • The encoding of the file itself
  • The encoding used at save time
  • The editor’s interpretation
  • The console’s input and output code pages
  • The app’s internal string format
  • The locale and assumed encoding on the Linux side
The layers to separate when isolating mojibakeA diagram showing that saying it got garbled on Windows is not enough to pin down a cause, and that you need to separate which of the file itself, the save-time encoding, the editor, the console code page, the app internals or the Linux locale is out of line.It got garbled on WindowsWhich one is out of lineThe encoding of the file itselfThe encoding used at save timeThe editor interpretationThe console code pageThe app internal string formatThe Linux locale

Figure 2: “It got garbled” does not settle the cause on its own. You have to look at which of these six layers slipped.

1.1 Terms used up front

Here are short definitions for the abbreviations that appear later without explanation.

Term Expansion What it means in this article
BOM Byte Order Mark A few marker bytes at the start of a file. It tells the reader which Unicode encoding is in use, and for UTF-16 it also indicates byte order. For UTF-8 it is optional
code page - The Windows mechanism that records, as a number, which legacy encoding to interpret text with. CP932 on Japanese Windows is one of those numbers
ANSI - The Windows way of saying “whatever the active code page is at the time.” What it actually resolves to depends on the environment; on a Japanese setup it is CP932
locale - A setting that bundles the defaults for language, region, and encoding. On Linux you set it through LANG or LC_ALL, and it includes the encoding, as in ja_JP.UTF-8
WSL Windows Subsystem for Linux The mechanism for running Linux on Windows. The Windows-side and Linux-side assumptions live together on one machine, which makes it a prime location for the breakage in this article
ETL Extract / Transform / Load Processing that pulls data out, transforms it, and writes it back. Because it re-reads and re-saves files along the way, it is a point where the encoding can change

Knowledge map for this article

Mojibake on Windows comes from a mismatch between the assumptions used to encode and to decode, such as reading a byte sequence saved as CP932 as if it were UTF-8 or the other way around, and the corruption looks different depending on the direction. Windows PowerShell is split between 5.1 and 7 or later in its default encoding, and differences per path, such as Out-File writing UTF-16LE while Set-Content writes CP932, are a breeding ground for accidents. The Linux side reads on the assumption of UTF-8 according to the locale, so handing it CP932 or UTF-8 with a BOM leads to mojibake and data corruption, and this happens especially easily in environments where the two live side by side, such as WSL. As long as the original bytes are still there, the data can be recovered by reading it back with the correct encoding and writing it out again with iconv or PowerShell, but once the misread content has been saved it cannot be restored. The article recommends making UTF-8 the first choice for new files and writing the encoding itself down explicitly as part of the I/O contract.

Windows mojibake and Linux integrationDiagram showing how the symptoms of mojibake change depending on whether the same byte sequence is read as CP932 or as UTF-8, how the BOM, UTF-16LE, and the console code page are independent layers, how the default encoding differs by PowerShell version, and how recovery with iconv or PowerShell relates to an operational policy that makes UTF-8 the first choicemay causemay causemay causemay causemay causemay causeusesusesusesmitigatesmitigatesverified byusesusesmay causerecommended forrecommended forMojibake (Garbled Text)CP932 (Windows-31J)UTF-8UTF-16LEBOM (Byte Order Mark)Corruption from Saving Mis-Decoded TextConsole Code PageWindows PowerShell 5.1PowerShell 7iconvPowerShellLinux Locale (LANG/LC_ALL)WSL (Windows Subsystem for Linux)Encoding as an I/O ContractUTF-8 First Policy for New Files

In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (17 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle

2. What Mojibake Actually Is

What mojibake actually is turns out to be quite simple.

  1. A string is encoded with some encoding into a byte sequence
  2. That byte sequence is decoded with some encoding back into a string
  3. If the encode and decode assumptions do not match, it reads as a different string
Whether the encode and decode assumptions matchA diagram showing that when a byte sequence produced by encoding a string is turned back into a string by decoding, a matching assumption returns the original string while a mismatched one produces a different string.MatchMismatchEncode a string into bytesDecode the bytes back into a stringDo the assumptions matchYou get the original string backIt reads as a different string

Figure 3: All mojibake really is is a mismatch between the encode assumption and the decode assumption.

For example, saving as UTF-8 produces these bytes.

E3 81 82

Read those bytes as UTF-8 and you get ; read them in a CP932 context and they look like a different string, something like 縺�. That is mojibake.

The point to hold onto is that what happened here is not that “the Japanese broke.” It is only that the interpretation of the same bytes diverged.

It is worth looking at the reverse direction too. Saving as CP932 gives these bytes.

82 A0

Try to read those bytes as UTF-8 and neither 0x82 nor 0xA0 is valid as a UTF-8 lead byte, so both become replacement characters and you see something like ��. From the UTF-8 side, this is not even a valid character to begin with.

A slightly longer example brings out the pattern better. Saving 日本語 as CP932 gives this.

93 FA 96 7B 8C EA

Read as UTF-8, that comes out as something like ���{��. The thing to notice is that only the fourth byte, 0x7B, passes straight through as the ASCII {. The second byte of a CP932 pair can fall in the ASCII range, which is why symbols such as { or \ end up scattered through the garbled result.

So the symptoms differ by direction.

Actual bytes Reader’s assumption What it looks like
UTF-8 CP932 A run of plausible-looking kanji and katakana, such as
CP932 UTF-8 Wall-to-wall replacement characters , with the occasional ASCII symbol such as { mixed in

A run of unreadable kanji means UTF-8 is being read as CP932; a wall of replacement characters means CP932 is being read as UTF-8. That asymmetry gives you a quick first guess, so it is worth remembering when you are isolating a problem.

2.1 If only the display is garbled, recovery may still be possible

Mojibake has a stage where things can still be recovered. If the original bytes are unchanged, for instance, reopening the file with the correct encoding can bring it back.

What is dangerous is a flow like this.

  1. A UTF-8 file is misread as CP932
  2. On screen it looks like 縺�
  3. The string as displayed is saved as-is
  4. The original UTF-8 bytes are gone

Once you are past that point, it is no longer a display issue. It is data corruption.

How a garbled display turns into data corruptionA diagram showing that going from the stage where a UTF-8 file is misread as CP932 and looks garbled on screen to the stage where the displayed string is saved and the original UTF-8 bytes are lost turns a display problem into data corruption.A UTF-8 file is misread as CP932It looks garbled on screenThe displayed string is saved as-isThe original UTF-8 bytes are goneUp to here reopening it still recovers itFrom here on it is data corruption

Figure 4: A misread alone is recoverable, but the moment the misread content is saved it turns into data corruption.

2.2 More dangerous still: dropping unrepresentable characters into a narrow code page

The other classic failure is converting a Unicode string down into a legacy code page such as CP932.

When the string contains characters that do not exist in the destination code page, you get things like:

  • replacement with ?
  • the replacement character appearing
  • conversion to a similar but different character
  • outright conversion failure

This one should be judged not by readable versus unreadable but by whether a round-trip conversion returns the original. Once a character is lost, knowing the correct encoding afterwards cannot bring it back.

What goes wrong when dropping into a narrow code pageA diagram showing that converting a Unicode string into a narrow code page such as CP932 turns characters missing from the destination into substitutions or conversion failures, that a round trip no longer returns the original, and that a character once lost cannot be restored even when the correct encoding is known.Unicode stringConvert into a narrow code page such as CP932Characters missing from the destinationTurn into ? or a replacement characterBecome a different character or fail to convertA round trip no longer returns the originalLost characters cannot be restored

Figure 5: Judge a drop into a narrow code page not by readable versus unreadable but by whether a round trip returns the original.

3. Why Things Get So Tangled on Windows

Windows is tangled not simply because it is old, but because the Unicode world and the legacy code page world still live side by side.

3.1 The Windows API carries both a Unicode lineage and a code page lineage

The Windows API has two broad lineages.

  • The W family: wide character. Handles Unicode as UTF-16
  • The A family: the code page lineage, known as ANSI

In other words, Windows has had both a path that handles text as Unicode and a path that handles it through the active code page from the very beginning. So even on one Windows machine, the assumption changes depending on which API and which tool the text went through.

The two lineages of the Windows APIA diagram showing that the Windows API has always had both a W family that handles Unicode as UTF-16 and an A family that handles text through the active code page, so the assumption changes depending on which path the text took.Windows APIW family (wide character)A family (known as ANSI)Handles Unicode as UTF-16Handles text via the active code pageThe assumption changes with the path taken

Figure 6: Windows has had both the Unicode path and the code page path from the start.

3.2 “Japanese on Windows” is not one thing

In day-to-day work, four things get mixed up most often around Japanese text on Windows.

  • CP932: turns up constantly in legacy Japanese Windows text
  • UTF-8: increasingly common in newer text assets, on the web, and in cross-platform work
  • UTF-16LE: still perfectly normal in the context of Windows tools and APIs
  • The console code page: a separate layer that governs input and output for cmd.exe and some console tools

The important point here is that running chcp 65001 does not make your files UTF-8. Changing the console code page and what bytes an existing file holds are separate questions.

chcp 65001 and existing files are separate questionsA diagram showing that chcp 65001 only changes the console code page, that the bytes in an existing file are untouched, and that the console setting and the file contents are separate questions.Run chcp 65001The console code page changesThe bytes in an existing fileNothing changesThe console and the file are separate questions

Figure 7: All chcp 65001 changes is how the console interprets bytes. The bytes in the file stay exactly as they were.

Legacy Japanese Windows text is often loosely called Shift_JIS, but in practice keeping the name CP932 in your head keeps conversations from drifting. At minimum it makes explicit that you are talking about the Windows-derived Japanese legacy encoding.

3.3 File names and file contents are separate problems

When Japanese file names show up correctly on Windows, the reflex is to assume the contents must be fine too. That is where the danger is.

  • The layer that handles paths and file names
  • The layer that reads file contents
  • The layer that displays to the console

These three are distinct.

Japanese paths may work without a hitch while the file contents, saved as CP932, break when the Linux side reads them as UTF-8. Conversely, the contents may be UTF-8 and only the display breaks because the console code page does not match.

Drawn as a diagram, the layers look like this.

Writerapp / editor / scriptThe bytes in the filethe only fact hereReader A: editorauto-detected or specified encodingReader B: consoleinput and output code pageReader C: app internalsthe library default encodingReader D: Linux sideUTF-8 assumed per the localeOnly the display breaksre-saving turns it into corruptionOnly the display breaksthe file is intactThe processing result breaksand propagates downstreamDecode error or replacement characters

Figure 8: The only fact is the bytes in the file. The editor, the console, the app, and the Linux side are independent readers of it.

Two things are worth watching. The first is separating whether the breakage is in the byte sequence in the middle or in one of the readers on the right. The second is that the four readers on the right are independent of each other, so verifying one guarantees nothing about the other three. That shape is exactly why “it was readable in the console, so the editor will be fine” does not hold.

3.4 The defaults in PowerShell and nearby tools are not aligned either

A quiet multiplier of breakage on Windows is that the same act of “writing some text” produces different output bytes depending on the path taken.

The points to watch in particular:

  • Windows PowerShell 5.1 has no consistent default encoding
  • Some cmdlets and redirection produce UTF-16LE
  • Other paths use the active ANSI code page
  • PowerShell 7 and later defaults to UTF-8 no BOM

So “text produced by PowerShell” does not determine the encoding on its own. You have to know which version, which cmdlet, and which write path were used.

Which path produces which bytes is laid out in about_Character_Encoding on Microsoft Learn. Pulling out just the common ones:

Write path Default in Windows PowerShell 5.1 Default in PowerShell 7
Out-File, >, >> UTF-16LE (with BOM) UTF-8 no BOM
Set-Content, Add-Content (new or empty file) ANSI = the active code page. CP932 on a Japanese setup UTF-8 no BOM
Export-Csv ASCII. Non-ASCII characters are dropped UTF-8 no BOM
Export-Clixml, New-ModuleManifest UTF-16LE UTF-8 no BOM
New-Item -Type File -Value UTF-8 no BOM UTF-8 no BOM
Start-Transcript UTF-8 with BOM UTF-8 no BOM

The read side differs too. When reading a file with no BOM, Get-Content in 5.1 assumes ANSI, while Import-Csv and Select-String assume UTF-8. The assumption is split inside a single session.

What bites hardest in practice is that the same act of writing text produces UTF-16LE through Out-File and CP932 through Set-Content. The “binary-looking text riddled with NUL bytes” mentioned in 4.3 usually comes from the default behavior of > or Out-File.

The asymmetry on the read side of Windows PowerShell 5.1A diagram showing that when reading a file with no BOM, Get-Content in Windows PowerShell 5.1 assumes ANSI while Import-Csv and Select-String assume UTF-8, leaving the assumption split inside a single session.A file with no BOMRead with Get-ContentRead with Import-Csv or Select-StringAssumes ANSIAssumes UTF-8The assumption is split inside one session

Figure 9: In 5.1 the same BOM-less file gets a different assumed encoding depending on which cmdlet reads it.

There is one more: in 5.1, specifying -Encoding UTF8 still produces a BOM. To write UTF-8 without a BOM from 5.1, you go through .NET.

# Write UTF-8 no BOM from Windows PowerShell 5.1
$text = "Body text containing Japanese"
[System.IO.File]::WriteAllText(
    "C:\work\output.txt", $text,
    (New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false))

Passing $false to UTF8Encoding is what says do not emit a BOM. On PowerShell 7, -Encoding utf8NoBOM gives the same result.

4. Typical Failures When Linux Is in the Mix

It is not unusual for something that more or less worked on Windows alone to break the moment Linux is involved. The reason is simple: the Linux side carries a strong UTF-8 assumption.

4.1 Text saved as CP932 on Windows, read as UTF-8 on Linux

The most common one.

  • A legacy Windows app or an old operational process writes CSVs, TXT, or logs in CP932
  • Linux-side scripts and tools read them with a UTF-8 assumption per the locale
  • The result is decode errors, , or strings that mean nothing

The Linux tool is not at fault here. The root cause is that the bytes arrived with no agreement about their encoding attached.

CP932 text read as UTF-8 by LinuxA diagram showing that when a legacy Windows app or an old operational process writes CSVs or logs in CP932 and hands them over with no encoding agreement, Linux-side scripts and tools read them as UTF-8 per the locale and produce decode errors or replacement characters.A legacy app writes CSVs and logs in CP932Handed over with no encoding agreementThe Linux side reads as UTF-8 per the localeDecode error / replacement charactersThe root cause is the missing agreement

Figure 10: The Linux tool is not the problem. The problem is bytes handed over with no encoding agreement attached.

4.2 UTF-8 no BOM created on Linux or VS Code, treated as ANSI on Windows

The reverse direction breaks too.

  • A script, config, or text file is created as UTF-8 no BOM on Linux or in VS Code
  • Windows PowerShell 5.1 or a legacy tool treats the BOM-less file as the ANSI-side code page
  • Only the lines containing Japanese or other non-ASCII break

UTF-8 tends to take the blame here, but the actual cause is a reader in the chain that will not correctly infer BOM-less UTF-8.

UTF-8 no BOM being treated as ANSIA diagram showing that when Windows PowerShell 5.1 or a legacy tool reads a UTF-8 no BOM file created on Linux or in VS Code and treats it as the ANSI-side code page, only the lines containing Japanese or other non-ASCII break.Create UTF-8 no BOM on Linux or VS Code5.1 or a legacy tool reads itTreats the BOM-less file as ANSIOnly lines with non-ASCII breakThe cause is a reader that will not infer

Figure 11: In the reverse direction the cause is a reader that cannot infer BOM-less UTF-8.

4.3 Windows writes UTF-16LE and Linux says it does not look like text

This one comes up a lot as well.

  • Some Windows PowerShell 5.1 output or a legacy tool writes UTF-16LE
  • Linux-side text tools expect a single-byte UTF-8 stream
  • The result is “binary-looking text” riddled with NUL bytes

UTF-16LE itself is fine. It just often fails to mesh with the assumption of piping text straight into Linux text processing tools.

UTF-16LE looking like binary on the Linux sideA diagram showing that when UTF-16LE written by some Windows PowerShell 5.1 output or a legacy tool is fed to Linux text tools that expect a single-byte UTF-8 stream, it looks like binary-ish text riddled with NUL bytes.Some 5.1 output or a legacy toolWrites UTF-16LELinux text tools expect a single-byte streamNUL bytes mixed in and it looks like binary

Figure 12: UTF-16LE itself is fine, but it does not mesh with what Linux text processing assumes.

4.4 BOM presence causes friction too

A BOM is not the encoding itself, but in practice it matters a great deal.

  • Some Windows-side tools are helped by a BOM
  • Some Linux-side tools treat the BOM as extra leading bytes
  • The result is a broken first column or first line, invisible junk, or comparisons that no longer line up

With UTF-8 in particular, the same UTF-8 with and without a BOM is a different byte sequence. Saying “we moved to UTF-8” only settles half the operational rule.

The friction caused by BOM presenceA diagram showing that the same UTF-8 is a different byte sequence with and without a BOM, that some Windows-side tools are helped by a BOM while some Linux-side tools treat it as extra leading bytes, and that this produces friction such as a broken first line.The same UTF-8 with and without a BOM is different bytesSome Windows-side toolsSome Linux-side toolsAre helped by a BOMTreat it as extra leading bytesThe start breaks or invisible junk appears

Figure 13: “We moved to UTF-8” is not enough. It becomes a rule only once BOM presence is settled too.

4.5 Trusting what the console shows sends you off course

The other danger when crossing between Windows and Linux is the console.

  • The Windows console has input and output code pages
  • Linux terminals mostly run on a UTF-8 locale assumption
  • Going through WSL, SSH, containers, or CI adds more display paths

In that state, concluding “it was readable in the console, so the file is fine” or “it was garbled in the console, so the file is corrupted” is an easy way to get it wrong. It is safer to verify separately whether what you see is broken and whether the saved bytes are broken.

4.6 The typical failures in a table

Situation Actual bytes Reader’s assumption Typical symptom
CSV saved by a legacy Windows app CP932 The Linux side assumes UTF-8 , decode errors, Japanese that means nothing
A file created on Linux or in VS Code UTF-8 no BOM Windows PowerShell 5.1 treats it as ANSI Only the Japanese lines break
Some Windows PowerShell 5.1 output UTF-16LE or ANSI The Linux side expects UTF-8 text NUL bytes mixed in, binary-like behavior
A UTF-8 with BOM file UTF-8 + BOM Unix tools assume plain UTF-8 Only the first column breaks, stray characters appear
Trusting the console display alone Different assumptions for file and console The investigator judges by display alone The root-cause split goes wrong

5. Drive a Mojibake Investigation with These Four Questions

When a mojibake investigation stalls, going back to these four questions is the fastest way forward.

5.1 What are the original bytes

The first thing to look at is what bytes this file holds right now. You need the habit of looking at bytes rather than at appearance.

  • Is it UTF-8
  • Is it UTF-8 with BOM
  • Is it CP932
  • Is it UTF-16LE
  • Was it re-saved somewhere along the way and turned into something else

5.2 Who wrote it first, and under what assumption

Next, identify the original writer.

  • A legacy Windows app
  • PowerShell 5.1 or 7
  • A Linux script
  • VS Code
  • An export from Excel
  • Some piece of middleware, a batch job, or CI

Leave this vague and inferring the encoding becomes a matter of luck.

5.3 Who is reading it now, and under what assumption

You need not just the writer but the reader’s assumption as well.

  • Is the editor auto-detecting
  • Is PowerShell looking at the BOM
  • Is the Linux side treating it as UTF-8 per the locale
  • Is a library falling back to its default encoding
  • Is Encoding.UTF8 or cp932 being specified explicitly

This is where mojibake almost always originates.

5.4 Has the misread content already been saved

Finally, confirm whether the damage has stopped at the display stage.

  • Are the bytes still the original ones
  • Has someone saved the broken-looking content
  • Have ? or shown up in the diff
  • Has the whole file been rewritten in a different encoding

Fill in these four questions and the cause is usually visible.

The four questions of a mojibake investigationA diagram showing that working through the four questions of what the original bytes are, who wrote it first and under what assumption, who is reading it now and under what assumption, and whether the misread content has already been saved usually makes the cause of mojibake visible.Q1 What are the original bytesQ2 Who wrote it first and under what assumptionQ3 Who is reading it now and under what assumptionQ4 Has the misread content already been savedThe cause becomes visible

Figure 14: When an investigation stalls, going back to these four questions and filling them in order is the fastest route.

6. How to Repair a Broken File

Once the cause is visible, recovery is next. The first thing to settle here is whether the original bytes are still there.

  • The original bytes are still there: read the file back with the correct encoding and write it out in the target encoding, and it comes back. That is what this chapter covers
  • The misread result has already been saved: your only option is a backup or the Git history. Characters that became the replacement character or ? cannot be restored even once you know the correct encoding

So the very first thing to do is take a copy of what you are working on. Convert the copy and leave the original file untouched.

The first branch in a recoveryA diagram showing that recovery starts by checking whether the original bytes are still there, that if they are you read the file back with the correct encoding and write it out, that if the misread result has already been saved your only option is a backup or the Git history, and that the work is done on a copy.Still thereSaved and lostTake a copy firstAre the original bytes still thereRead back with the correct encoding and write it outRestore from a backup or the Git historyCharacters turned into replacement characters cannot be restored

Figure 15: Recovery starts with taking a copy, then forks on whether the original bytes are still there.

6.1 If you are on the Linux side, use iconv

To turn a CP932 file into UTF-8, iconv is the most straightforward option.

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

If you get the source encoding wrong, it stops partway with something like this.

iconv: illegal input sequence at position 0

Stopping is itself the information that the file is not in that encoding, so the fast move is to try another candidate. If nothing you pass makes it stop, the file may consist of ASCII only.

To go from UTF-16LE down to UTF-8, use -f UTF-16LE. Note, though, that converting a file that has a BOM while explicitly specifying -f UTF-16LE can leave the BOM in the output as a U+FEFF character. If you want iconv to handle the BOM for you, use -f UTF-16, and always check the first character after converting.

6.2 If you are on the Windows side, use PowerShell

From PowerShell 6.2 onward, you can pass a code page number directly to -Encoding. CP932 is 932.

# PowerShell 6.2 and later. CP932 -> UTF-8 no BOM
Get-Content -Path .\input.csv -Encoding 932 |
    Set-Content -Path .\output.csv -Encoding utf8NoBOM

Going the other way, to hand a UTF-8 file created on Linux to a consumer that can only read CP932:

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

This form has two side effects, though.

  1. Get-Content splits the input into lines and Set-Content re-appends a newline after each one. In other words, the newline convention gets normalized for you
  2. Even if the original file had no trailing newline, the output will have one

If you want to preserve the bytes right down to the newlines, handle the file whole instead of line by line.

# CP932 -> UTF-8 no BOM with the newlines preserved as-is
$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) works as-is on Windows PowerShell 5.1. In environments where it throws on PowerShell 7, run the following once first.

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

Whether to emit a BOM is decided by what the consumer needs, as covered in 7.1. Pass $true to UTF8Encoding in the example above and you get a BOM.

6.3 What to check after converting

A conversion is not finished just because no error appeared. At minimum, check the following.

  • Do two or three representative Japanese lines read correctly
  • Have ? or increased. If they have, those characters are already gone
  • Has the line count changed
  • Are the BOM and the newline convention what the consumer expects
  • Has the file size changed drastically. Going from CP932 to UTF-8 grows the Japanese portions from 2 bytes to 3 bytes per character, so a modest increase is normal

The second item matters most. Dropping UTF-8 that contains characters missing from CP932 down into CP932 always loses information. As written in 2.2, check all the way through to whether a round trip returns the original.

What to check after convertingA diagram showing that a conversion is not finished just because no error appeared, that you check whether representative Japanese lines read correctly, whether question marks or replacement characters have increased, and whether the line count, BOM, newlines and file size are as expected, and that an increase in replacement characters means those characters are already gone.The conversion finishes with no errorRead two or three representative Japanese linesHave ? or replacement characters increasedCheck line count, BOM, newlines and sizeIf they increased those characters are gone

Figure 16: A conversion is not done at “no error appeared.” Checking the contents is part of the same job.

6.4 Carve bulk conversion out as a migration task

A word on operations to close. Repairing one broken file and moving a whole repository from CP932 to UTF-8 are different jobs. As 7.2 in the next chapter says, do not do the latter on the side of an everyday functional fix. Plan it as an independent migration task.

7. Operational Rules That Reduce Breakage

From here on it is the practical side. In projects that span Windows and Linux, settling the following rules up front cuts the damage substantially.

7.1 Make UTF-8 the first choice for new files

For new text files, making UTF-8 the first candidate is the safe default. But do not stop there. You need to decide what happens with the BOM as well.

Here is a good way to decide.

  • Text that is mostly read on the Linux side: default to UTF-8 no BOM
  • Scripts read by legacy Windows tools or Windows PowerShell 5.1: state BOM presence explicitly based on what that consumer needs
  • If there is a specific consumer that requires UTF-16LE, write that requirement into the spec

Write down only “standardize on UTF-8” and you will be arguing about the BOM later.

How to decide the encoding for a new fileA diagram showing that a new text file starts with UTF-8 as the first choice but does not stop there, defaulting to UTF-8 no BOM when the Linux side reads it most, stating BOM presence explicitly when a legacy tool or Windows PowerShell 5.1 reads it, and writing the requirement into the spec when a consumer needs UTF-16LE.Mostly the Linux side5.1 or a legacy toolA consumer that needs UTF-16LEA new text fileMake UTF-8 the first choiceWho reads itDefault to UTF-8 no BOMState BOM presence per the consumerWrite the requirement into the spec

Figure 17: Start a new file at UTF-8, then finish the decision by settling BOM handling around what the consumer needs.

7.2 Keep existing legacy files as they are until an explicit migration task

If an existing file is CP932, it is safer not to quietly move it to UTF-8 alongside an everyday functional fix.

The safe operational shape is this.

  • Existing files keep their original encoding, BOM, and newlines
  • Encoding changes are carved out as a migration task
  • Convert in bulk only after confirming the targets, the blast radius, and the downstream consumers

Most mojibake damage begins with a well-intentioned “converting it to UTF-8 while I’m in here.”

7.3 Treat the encoding as part of the interface

For CSVs, TXT files, logs, configuration files, and simple protocols, the encoding itself is the interface, not just the content.

At minimum, a spec should state this much.

  • Is this file UTF-8, CP932, or UTF-16LE
  • If UTF-8, does it carry a BOM
  • Are the newlines LF or CRLF
  • Which side, Linux or Windows, is the producer and which the consumer
  • Does an intermediate batch job or ETL step re-save it

“We hand it over as text” is not a specification.

7.4 Do not trust defaults. Be explicit when writing

In code and in scripts alike, specifying the encoding explicitly is safer.

These are the dangerous lines of thinking.

  • Save with whatever the default is
  • It will probably come out fine, matching the OS
  • It was readable in the console, so the file is probably fine
  • Auto-detect exists, so it will be fine

Defaults change routinely between Windows and Linux, between PowerShell 5.1 and 7, and across editors and runtimes. Unless you are explicit, it tends to be working by coincidence.

7.5 Verify the console and the file separately

A quietly effective rule.

  • Checking the display in the console
  • Checking by reopening the file

Keep these two apart.

Even if chcp and the terminal display line up, it means nothing when the saved file is in a different encoding. Conversely, the file may be perfectly fine and only the appearance breaks because the console display code page does not match.

7.6 Git will not fix your encodings

Unglamorous, but important.

Git fundamentally tracks bytes. Which means it dutifully records broken bytes into history exactly as they are.

So when

  • a huge diff appears even though you changed nothing
  • only the Japanese lines produce a diff you cannot explain
  • only the first line changed
  • newlines and encoding changed together

it is better to suspect a re-encoding failure before suspecting a content change.

Git will not fix your encodingsA diagram showing that Git fundamentally only tracks bytes so broken bytes enter the history as they are, and that when a huge diff appears with no change or only the Japanese lines produce an unexplained diff you should suspect a re-encoding failure before a content change.Git only tracks bytesBroken bytes enter the history as they areHuge diffs or unexplained Japanese-only diffs appearSuspect a re-encoding failure before a content change

Figure 18: Git faithfully records broken bytes too, so an unexplained diff should point you at re-encoding first.

8. The Minimum Checklist

Here is the checklist worth pinning down first on a project where Windows and Linux are mixed.

The flow of the checklistA diagram showing the flow of checking the current encoding, BOM and newlines before editing, avoiding reliance on defaults and auto-detect while editing, and reopening the file and checking the diff after editing, with bulk conversion and similar work handled separately as a migration task.Before editing check the current encoding, BOM and newlinesWhile editing avoid leaving it to defaults or auto-detectAfter editing reopen the file and check the diffHandle bulk conversion separately as a migration task

Figure 19: Split the checks into before, during, and after editing, and carve bulk conversion out into its own task.

8.1 Before editing

  • What is this file’s current encoding
  • Is there a BOM
  • Are the newlines LF or CRLF
  • Have you noted two or three representative Japanese lines
  • Do you know which side, Linux or Windows, is the final consumer

8.2 While editing

  • Are you writing in a way that depends on the default encoding
  • Are you saving with auto-detect left in charge
  • Are you being careless about PowerShell or shell redirection paths
  • Are you taking comfort in nothing more than “the display is readable”

8.3 After editing

  • Did you reopen the file and check after saving
  • Are the representative lines intact on both the Linux and the Windows side
  • Have ? or increased in the diff
  • Is only the first line or the first column broken
  • Is the diff a huge BOM-only or newline-only change

8.4 What belongs in a migration task

  • Bulk conversion from CP932 to UTF-8
  • Unifying the UTF-8 BOM policy
  • Taking stock of scripts that assume PowerShell 5.1
  • Documenting the text paths that go through CI, containers, WSL, or SSH
  • Unifying the save settings of editors, formatters, and batch jobs

9. Summary

If the Windows text encoding problem had to be put in one sentence, the essence is that the Unicode world and the legacy code page world still live side by side.

And the reason more breaks once Linux is added is that the Linux side mostly flows on a UTF-8 assumption, which brings the Windows side’s CP932, UTF-16, console code page, and PowerShell version differences all into the open at once.

Five points are worth remembering.

  • Mojibake is a divergence in how bytes are interpreted
  • A garbled display and data corruption are different things
  • On Windows, think in separate layers: file, editor, console, API
  • For text exchanged with Linux, make UTF-8 the first choice
  • Keep the conversion of existing legacy files apart from ordinary maintenance

Taken at face value, “it got garbled on Windows” covers far too much ground. But cut it along

  • what the original bytes are
  • who wrote it and how
  • who read it and how
  • whether it has already been saved

and it becomes far more tractable.

Text encodings are unglamorous, but between Windows and Linux they are the I/O contract itself. Refusing to leave that ambiguous is the single most effective countermeasure.

10. References

Windows / Microsoft

PowerShell / VS Code

GNU / Linux locale

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Technical Consulting & Design Review

In projects where the encoding assumptions for CSVs, logs, and configuration files diverge between Windows and Linux, sorting out the I/O contract and the operational rules first is an effective way to reduce breakage.

Frequently Asked Questions

Common questions about the topic of this article.

What causes mojibake on Windows?
Almost all of it comes from reading the same byte sequence as a different encoding, or from saving the result of a misread in yet another encoding. For example, the bytes produced by saving the character あ as UTF-8 (E3 81 82) look like an unrelated string such as 縺 when read in a CP932 context. It does not happen because Japanese is hard; the real cause is that the encode and the decode assumptions do not match.
Can a file that is already garbled be restored?
If the original bytes have not changed, reopening the file with the correct encoding can bring it back. The dangerous path is saving the misread, broken-looking content as-is: once you are past that point it is no longer a display problem but data corruption. And when a Unicode string is dropped into a narrow code page such as CP932 and characters turn into ? or a replacement character, those lost characters cannot be recovered later, even once you know the correct encoding.
Does chcp 65001 make my files UTF-8 as well?
No. Changing the console code page and what bytes an existing file holds are separate questions. On Windows, a file's own encoding, the editor's interpretation, the console's input and output code pages, and the app's internal string format are different things, and you have to keep those layers apart. Concluding that a file must be fine because it displayed correctly in the console is an easy way to get it wrong, so checking the console display and checking by reopening the file should be treated as two separate steps.
What are the safe rules for moving text between Windows and Linux?
Make UTF-8 the first choice for new files, and settle the BOM question as part of that decision. Text that is mostly read on the Linux side should default to UTF-8 no BOM; when legacy tools such as Windows PowerShell 5.1 read it, state BOM presence explicitly based on what that consumer needs. Do not convert existing CP932 files on the side while doing everyday maintenance - separate that out as an explicit migration task. And because for CSVs and logs the encoding itself is the interface, writing the encoding, the BOM, and the newline convention into the spec keeps things from breaking.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog