更新履歴(5件・最終更新 2026年08月23日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- encode/decodeの前提のずれから復旧手順・運用ルールまでを図でも追えるように、Mermaid図を18点追加しました(地の文500〜750字につき1図の規約に合わせたものです)。既存の経路図にもキャプションを付け、図番号を通しで振り直しました。本文の文章は変えていません。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- 壊れたファイルをどう直すかの章を新設しました(`iconv`とPowerShell、変換後の確認。`Get-Content`と`Set-Content`をつなぐと改行が揃ってしまう副作用も明記しています)。用語ミニ辞書、PowerShellの版差による既定エンコーディングの比較表(読み込み側が非対称であることを含む)、逆方向のバイト例を追加し、どの層で化けるかの経路図を置きました。
- 参考リンクなどで縦棒(パイプ)記号を含む行が表として表示され、リンクが押せなくなっていた表示崩れを修正しました。本文の内容は変えていません。
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589718)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「Windows文字コード入門 - Linux連携で起きる文字化け」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589718 https://comcomponent.com/blog/2026/03/21/000-windows-text-encoding-mojibake-linux/
- DOI(最新版)
- 10.5281/zenodo.21589718
- DOI(この版)
- 10.5281/zenodo.22064720
Windows の文字化けは、日本語が難しいから起きるわけではありません。ほとんどは、同じバイト列を別の文字コードとして読んだか、誤って読んだ結果を別の文字コードで保存したことが原因です。
特に Windows と Linux をまたぐと、Windows 側には CP932、UTF-8、UTF-16、console の code page、PowerShell の版差など複数の文脈が残り、Linux 側は UTF-8 前提で流れることが多いため、ふだん見えていなかった前提のずれが一気に表面化します。
flowchart TB
accTitle: WindowsとLinuxで前提がずれる構図
accDescr: Windows側にはCP932やUTF-16、consoleのcode page、PowerShellの版差など複数の文脈が残り、UTF-8前提で流れることが多いLinux側と組み合わせると前提のずれが表面化することを示す図。
w1["Windows側"] --> w2["CP932 / UTF-16 / code page / 版差"]
l1["Linux側"] --> l2["UTF-8前提が強い"]
w2 --> z1["前提のずれが表面化"]
l2 --> z1
図1: Windows側に残る複数の文脈と、Linux側のUTF-8前提が出会うところで、ずれが一気に表面化する。
この話は、日本語処理の難しさというより、bytes をどの前提で扱っているかを揃えられているかどうかの話です。この記事では、Windows の文字コードまわりを「なぜ文字化けが起きるのか」という観点で整理し、特に Linux と組み合わせたときに事故が増えるポイントを実務寄りにまとめます。
対象読者は、Windows で作った CSV / ログ / 設定ファイルを Linux 側へ渡している、あるいはその逆をしていて、文字化けの切り分けと復旧を自分でやりたい方です。特定の言語やフレームワークの知識は前提にしていません。コマンド例は iconv と PowerShell を使います。
1. まず押さえたいこと
先に要点だけ書くと、重要なのは次の 6 点です。
- 文字化けは「文字」の問題ではなく、「バイト列をどう解釈したか」の問題です。
- Windows には Unicode 系と legacy code page 系が共存しており、1 台の中でも文脈ごとに前提が違います。
- Linux 側は UTF-8 前提が強いため、Windows 側の CP932 や UTF-16 が混ざると事故になりやすいです。
- 表示が崩れただけの段階と、壊れた内容を保存してしまった段階は分けて考えるべきです。
- 新規テキストは UTF-8 を第一候補にし、既存の legacy ファイルは明示的な移行タスクまで現状維持にするのが安全です。
- file の encoding、editor の encoding、console の code page、アプリ内部の文字列形式は別物です。ここを混同すると調査が迷子になります。
「Windows で文字化けした」という言い方だけでは原因は特定できません。少なくとも次のどれがずれているかを分ける必要があります。
- ファイル自体の文字コード
- 保存時の文字コード
- エディタの解釈
- console の input/output code page
- アプリ内部の文字列形式
- Linux 側の locale と想定 encoding
flowchart TB
accTitle: 文字化けの切り分けで分ける層
accDescr: 「Windowsで文字化けした」という言い方だけでは原因を特定できず、ファイル自体・保存時・エディタ・consoleのcode page・アプリ内部・Linuxのlocaleのどれがずれているかを分ける必要があることを示す図。
s0["Windowsで文字化けした"] --> q1{"どこがずれたか"}
q1 --> a1["ファイル自体の文字コード"]
q1 --> a2["保存時の文字コード"]
q1 --> a3["エディタの解釈"]
a1 --> a4["consoleのcode page"]
a2 --> a5["アプリ内部の文字列形式"]
a3 --> a6["Linuxのlocale"]
図2: 「文字化けした」だけでは原因は決まらず、少なくともこの6つの層のどれがずれたかを分けて見る。
1.1 先に出てくる用語
本文で断りなく出てくる略語を、先に短くまとめておきます。
| 用語 | フルスペル | この記事での意味 |
|---|---|---|
| BOM | Byte Order Mark | ファイル先頭に置く数バイトの印。どの Unicode encoding かを読み手へ伝えるもので、UTF-16 では加えてバイト順も示します。UTF-8 では付けても付けなくても構いません |
| code page | - | Windows が「どの legacy 文字コードで解釈するか」を番号で持っている仕組み。日本語 Windows の CP932 はその番号のひとつです |
| ANSI | - | Windows で「そのときの active code page」を指す言い方。実体は環境で変わり、日本語環境なら CP932 です |
| locale | - | 言語・地域・文字コードの既定をまとめた設定。Linux では LANG や LC_ALL で指定し、ja_JP.UTF-8 のように encoding まで含みます |
| WSL | Windows Subsystem for Linux | Windows 上で Linux を動かす仕組み。Windows 側と Linux 側の前提が 1 台の中で同居するので、この記事の事故が起きやすい場所です |
| ETL | Extract / Transform / Load | データを取り出し、変換し、書き戻す処理のこと。途中でファイルを読み直して保存し直すので、encoding が変わる地点になります |
この記事の知識マップ
Windowsの文字化けは、CP932で保存したバイト列をUTF-8として読む、あるいはその逆というencodeとdecodeの前提のずれから生じ、方向によって崩れ方の症状が異なります。Windows PowerShellは5.1と7以降で既定のencodingが割れており、Out-FileがUTF-16LEを、Set-ContentがCP932を書くといった経路ごとの違いが事故の温床になります。Linux側はlocaleに従いUTF-8を前提に読むため、CP932やBOM付きUTF-8を渡すと文字化けやデータ破損につながり、WSLのように両者が同居する環境ではとくに起きやすくなります。元のバイト列が残っていればiconvやPowerShellで正しいencodingとして読み直し書き出すことで復旧できますが、誤読した内容を保存してしまうと復元できません。記事は新規ファイルをUTF-8第一候補にし、encoding自体をI/Oの契約として明文化する運用を推奨しています。
flowchart LR
accTitle: Windowsの文字化けとLinux連携の知識マップ
accDescr: 同じバイト列をCP932とUTF-8のどちらで読むかで文字化けの症状が変わること、BOMやUTF-16LEやコンソールのコードページが独立した層であること、PowerShellのバージョンで既定のencodingが異なること、iconvやPowerShellによる復旧とUTF-8を第一候補にする運用方針の関係を示す図
mojibake["文字化け"]
cp932["CP932"]
utf_8["UTF-8"]
utf_16le["UTF-16LE"]
bom["BOM(Byte Order Mark)"]
encoding_overwrite_corruption["誤読内容の上書き保存によるデータ破損"]
console_code_page["コンソールのコードページ"]
windows_powershell_5_1["Windows PowerShell 5.1"]
powershell_7["PowerShell 7"]
iconv["iconv"]
powershell["PowerShell"]
linux_locale["Linuxのlocale"]
wsl["WSL(Windows Subsystem for Linux)"]
encoding_as_interface["encodingをI/O契約として明文化する運用"]
utf8_first_policy["新規ファイルをUTF-8第一候補にする運用方針"]
cp932 -.->|"原因になり得る"| mojibake
utf_8 -.->|"原因になり得る"| mojibake
utf_16le -.->|"原因になり得る"| mojibake
bom -.->|"原因になり得る"| mojibake
mojibake -.->|"原因になり得る"| encoding_overwrite_corruption
console_code_page -.->|"原因になり得る"| mojibake
windows_powershell_5_1 -->|"利用する"| utf_16le
windows_powershell_5_1 -->|"利用する"| cp932
powershell_7 -->|"利用する"| utf_8
iconv -.->|"軽減する"| mojibake
powershell -.->|"軽減する"| mojibake
mojibake -->|"で確認できる"| iconv
linux_locale -.->|"利用する"| utf_8
wsl -->|"利用する"| linux_locale
wsl -.->|"原因になり得る"| mojibake
encoding_as_interface -->|"推奨される対応"| mojibake
utf8_first_policy -->|"推奨される対応"| mojibake
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全17件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 文字化けの正体
文字化けの正体は、かなり単純です。
- 文字列をどこかの文字コードで encode してバイト列にする
- そのバイト列をどこかの文字コードで decode して文字列に戻す
- encode と decode の前提が一致しなければ、別の文字列として読まれる
flowchart TB
accTitle: encodeとdecodeの前提の一致
accDescr: 文字列をencodeしてできたバイト列をdecodeで文字列に戻すとき、両者の前提が一致すれば元の文字列に戻り、ずれていれば別の文字列として読まれることを示す図。
e1["文字列をencodeしてバイト列に"] --> e2["バイト列をdecodeして文字列に"]
e2 --> j1{"前提は一致しているか"}
j1 -->|"一致"| r1["元の文字列に戻る"]
j1 -->|"ずれ"| r2["別の文字列に読まれる"]
図3: 文字化けの正体は、encodeとdecodeの前提が一致していないこと。
たとえば、あ を UTF-8 で保存すると、バイト列は次になります。
E3 81 82
このバイト列を UTF-8 として読めば あ ですが、CP932 側の文脈で読めば 縺� のような別の文字列に見えます。これが文字化けです。
大事なのは、ここで起きているのが「日本語が壊れた」ではなく、同じ bytes に対する解釈がずれただけだという点です。
逆向きも見ておきます。あ を CP932 で保存すると、バイト列はこうなります。
82 A0
このバイト列を UTF-8 として読もうとすると、0x82 も 0xA0 も UTF-8 の先頭バイトとして成立しないため、どちらも置換文字になり �� のように見えます。UTF-8 側から見ると、そもそも 文字として成立していない わけです。
もう少し長い例のほうが特徴が出ます。日本語 を CP932 で保存すると、バイト列はこうです。
93 FA 96 7B 8C EA
これを UTF-8 として読むと ���{�� のようになります。ここで見てほしいのは、4 バイト目の 0x7B だけが ASCII の { として素通りしている ことです。CP932 の 2 バイト目には ASCII 範囲の値が来ることがあるため、化けた結果に { や \ のような記号が紛れ込みます。
つまり、方向によって症状が違います。
| 実際のバイト列 | 読み手の想定 | 見え方 |
|---|---|---|
| UTF-8 | CP932 | 縺 のように、それらしい漢字やカタカナが並ぶ |
| CP932 | UTF-8 | 置換文字 � だらけになり、ときどき { のような ASCII 記号が混ざる |
「読めない漢字が並んでいるなら UTF-8 を CP932 で読んでいる」「置換文字だらけなら CP932 を UTF-8 で読んでいる」 と当たりを付けられるので、この非対称性は覚えておくと切り分けが速くなります。
2.1 表示が崩れただけなら、まだ戻せることがある
文字化けには、まだ取り返せる段階があります。たとえば、元の bytes が変わっていないなら、正しい encoding で開き直せば戻せる場合があります。
逆に危ないのは、次のような流れです。
- UTF-8 の file を CP932 として誤読する
- 画面上では
縺�のように見える - そのまま「見えている文字列」を保存する
- もとの UTF-8 bytes が失われる
この段階に入ると、単なる表示崩れではなく、データ破損です。
flowchart TB
accTitle: 表示崩れがデータ破損に変わる流れ
accDescr: UTF-8のfileをCP932として誤読して画面上で化けて見えている段階から、見えている文字列をそのまま保存して元のUTF-8のバイト列が失われる段階へ進むと、表示崩れではなくデータ破損になることを示す図。
d1["UTF-8のfileをCP932として誤読"] --> d2["画面上では化けて見える"]
d2 --> d3["見えている文字列をそのまま保存"]
d3 --> d4["元のUTF-8のバイト列が失われる"]
d2 -.-> n1["ここまでなら開き直せば戻せる"]
d4 -.-> n2["ここからはデータ破損"]
図4: 誤読しただけなら戻せるが、誤読した内容を保存した時点でデータ破損に変わる。
2.2 さらに危ないのは「表現できない文字」を狭い code page に落とすとき
もう 1 つの典型事故は、Unicode 文字列を CP932 のような legacy code page に落とすときです。
たとえば、相手の code page に存在しない文字が含まれていると、
?に置き換わる- 置換文字
�が入る - 近い別文字に変換される
- 変換失敗になる
といったことが起きます。
この事故は、読める・読めないだけでなく、往復変換して元に戻るかで見るべきです。一度失われた文字は、あとから正しい encoding を知っても復元できません。
flowchart TB
accTitle: 狭いcode pageに落とすときの事故
accDescr: Unicode文字列をCP932のような狭いcode pageへ変換すると、相手のcode pageに存在しない文字が置き換えや変換失敗になり、往復変換しても元に戻らず、一度失われた文字は正しいencodingを知っても復元できないことを示す図。
u1["Unicode文字列"] --> u2["CP932のような狭いcode pageへ変換"]
u2 --> j1{"相手に存在しない文字は"}
j1 --> r1["?や置換文字に変わる"]
j1 --> r2["別文字化・変換失敗"]
r1 --> k1["往復変換しても元に戻らない"]
r2 --> k1
k1 -.-> n1["失われた文字は復元できない"]
図5: 狭いcode pageへ落とす事故は、読める・読めないではなく往復変換して戻るかで見る。
3. なぜ Windows ではややこしくなりやすいのか
Windows がややこしいのは、単に古いからではありません。Unicode の世界と legacy code page の世界が、いまも同居しているからです。
3.1 Windows API には Unicode 系と code page 系が共存している
Windows API には大きく 2 系統あります。
W系: wide character。Unicode を UTF-16 で扱う系A系: ANSI と呼ばれる code page 系
つまり、Windows の中には最初から「Unicode で扱う道」と「その時点の active code page で扱う道」が両方あります。そのため、同じ Windows 上でも、どの API やどのツールを通ったかで前提が変わります。
flowchart TB
accTitle: Windows APIの2系統
accDescr: Windows APIにはUnicodeをUTF-16で扱うW系と、そのときのactive code pageで扱うA系が最初から共存しており、どの経路を通ったかで前提が変わることを示す図。
api["Windows API"] --> w1["W系(wide character)"]
api --> a1["A系(ANSIと呼ばれる系)"]
w1 --> w2["UnicodeをUTF-16で扱う"]
a1 --> a2["active code pageで扱う"]
w2 --> z1["通った経路で前提が変わる"]
a2 --> z1
図6: Windowsの中にはUnicodeの道とcode pageの道が最初から両方ある。
3.2 「Windows の日本語」は 1 個ではない
Windows の日本語まわりで、実務上よく混ざるのは次の 4 つです。
- CP932: 日本語 Windows の legacy text でよく出る
- UTF-8: 新しい text 資産、web、cross-platform 系で増えている
- UTF-16LE: Windows 系ツールや API の文脈で今も普通に出てくる
- console の code page:
cmd.exeや一部の console tool の入出力に効く別レイヤ
ここで大事なのは、chcp 65001 したから file も UTF-8 になった、ではないということです。console の code page を変えることと、既存 file の bytes が何かは別問題です。
flowchart TB
accTitle: chcp 65001と既存fileは別問題
accDescr: chcp 65001はconsoleのcode pageを変えるだけで、既存fileのバイト列は何も変わらず、consoleの設定とfileの中身は別問題であることを示す図。
c1["chcp 65001を実行"] --> c2["consoleのcode pageが変わる"]
f1["既存fileのバイト列"] --> f2["何も変わらない"]
c2 --> n1["consoleとfileは別問題"]
f2 --> n1
図7: chcp 65001で変わるのはconsoleの解釈だけで、fileのバイト列は元のまま。
なお、日本語 Windows の legacy text を雑に「Shift_JIS」と呼ぶことは多いですが、実務では CP932 という名前で意識しておくほうが会話がぶれにくいです。少なくとも「Windows 由来の日本語 legacy encoding の話をしている」と明示できます。
3.3 file 名と file 内容は別問題
Windows で日本語 file 名が普通に見えていると、「じゃあ中身も大丈夫だろう」と思いがちです。ここが危険です。
- path / file name を扱う層
- file の中身を読む層
- console に表示する層
この 3 つは別です。
たとえば、日本語 path は問題なく扱えても、file の中身は CP932 で保存されていて Linux 側で UTF-8 として読まれれば壊れます。逆に file の中身が UTF-8 でも、console の code page が合っていなければ表示だけ崩れます。
層の関係を図にすると、こうなります。
flowchart LR
W["書き手<br/>アプリ / エディタ / スクリプト"] --> FB["ファイルのバイト列<br/>ここだけが事実"]
FB --> R1["読み手A: エディタ<br/>自動判定または指定 encoding"]
FB --> R2["読み手B: console<br/>入出力の code page"]
FB --> R3["読み手C: アプリ内部<br/>ライブラリの既定 encoding"]
FB --> R4["読み手D: Linux 側<br/>locale に従い UTF-8 前提"]
R1 --> S1["表示だけ崩れる<br/>保存し直すと破損に変わる"]
R2 --> S2["表示だけ崩れる<br/>ファイルは無事"]
R3 --> S3["処理結果が壊れる<br/>下流へ伝播する"]
R4 --> S4["decode error か置換文字"]
図8: 事実はファイルのバイト列だけで、エディタ・console・アプリ・Linux側の読み手は互いに独立している。
見るべきポイントは 2 つです。1 つは、壊れているのは真ん中のバイト列なのか、右側の読み手なのか を分けること。もう 1 つは、右側の 4 つは互いに独立していて、1 つで確認できても他の 3 つの保証にはならない ことです。「console で読めたからエディタでも大丈夫」が成り立たないのは、この形のためです。
3.4 PowerShell や周辺ツールの既定値も揃っていない
Windows で地味に事故を増やすのが、同じ「テキストを書いたつもり」でも経路によって出力 bytes が違うことです。
特に気をつけたいのはこのあたりです。
- Windows PowerShell 5.1 は既定 encoding が一貫していない
- 一部 cmdlet や redirection は UTF-16LE を作る
- 別の経路では active ANSI code page が使われる
- PowerShell 7 以降は UTF-8 no BOM が既定になっている
つまり、「PowerShell で出した text」だけでは encoding は決まりません。どの版で、どの cmdlet で、どの書き込み経路を使ったかまで見ないといけません。
どの経路がどんな bytes を作るかは、Microsoft Learn の about_Character_Encoding に整理されています。よく使うものだけ抜き出すと、こうなります。
| 書き込み経路 | Windows PowerShell 5.1 の既定 | PowerShell 7 の既定 |
|---|---|---|
Out-File、>、>> |
UTF-16LE(BOM 付き) | UTF-8 no BOM |
Set-Content、Add-Content(新規または空ファイル) |
ANSI = active code page。日本語環境なら CP932 | UTF-8 no BOM |
Export-Csv |
ASCII。非 ASCII は落ちます | 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 |
読み込み側にも差があります。BOM の無いファイルを読むとき、5.1 の Get-Content は ANSI とみなしますが、Import-Csv と Select-String は UTF-8 とみなします。同じセッションの中で前提が割れているわけです。
ここで実務上いちばん刺さるのは、同じ「テキストを書いた」でも Out-File は UTF-16LE、Set-Content は CP932 になる という点です。4.3 で挙げた「NUL byte が大量に混ざったバイナリっぽい text」は、たいてい > か Out-File の既定から来ています。
flowchart TB
accTitle: Windows PowerShell 5.1の読み込み側の非対称
accDescr: BOMの無いファイルを読むとき、Windows PowerShell 5.1のGet-ContentはANSIとみなす一方で、Import-CsvとSelect-StringはUTF-8とみなし、同じセッションの中で前提が割れていることを示す図。
b1["BOMの無いファイル"] --> g1["Get-Contentで読む"]
b1 --> i1["Import-Csvや Select-Stringで読む"]
g1 --> g2["ANSIとみなす"]
i1 --> i2["UTF-8とみなす"]
g2 --> z1["同じセッション内で前提が割れる"]
i2 --> z1
図9: 5.1では同じBOMなしファイルでも、読むcmdletによって想定encodingが違う。
もう 1 つ、5.1 では -Encoding UTF8 を指定しても BOM 付き になります。5.1 で BOM なしの UTF-8 を書きたい場合は、.NET 側で書くことになります。
# Windows PowerShell 5.1 で UTF-8 no BOM を書く
$text = "日本語を含む本文"
[System.IO.File]::WriteAllText(
"C:\work\output.txt", $text,
(New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false))
UTF8Encoding の引数を $false にしているのが、BOM を付けない指定です。PowerShell 7 なら -Encoding utf8NoBOM で同じ結果になります。
4. Linux と組み合わせたときの典型事故
Windows 単体だと何となく回っていたものが、Linux を挟んだ途端に壊れるのは珍しくありません。理由は単純で、Linux 側では UTF-8 前提が強いからです。
4.1 Windows で CP932 保存した text を Linux が UTF-8 として読む
一番よくある事故です。
- Windows の legacy app や古い運用が CP932 で CSV / TXT / log を書く
- Linux 側の script や tool は locale に従って UTF-8 前提で読む
- 結果として decode error、
�、意味不明な文字列になる
このとき Linux 側の tool が悪いのではなく、受け取った bytes に encoding の約束が付いていないのが根本原因です。
flowchart TB
accTitle: CP932のtextをLinuxがUTF-8として読む事故
accDescr: Windowsのlegacy appや古い運用がCP932でCSVやlogを書き、encodingの約束が無いままLinux側のscriptやtoolがlocaleに従ってUTF-8前提で読むと、decode errorや置換文字になることを示す図。
p1["legacy appがCP932で CSV / logを書く"] --> p2["encodingの約束なしで渡す"]
p2 --> p3["Linux側はlocaleに従い UTF-8前提で読む"]
p3 --> p4["decode error / 置換文字"]
p2 -.-> n1["根本原因は約束の欠如"]
図10: 悪いのはLinux側のtoolではなく、bytesにencodingの約束が付いていないこと。
4.2 Linux / VS Code で作った UTF-8 no BOM を Windows 側が ANSI と見なす
逆方向の事故もあります。
- Linux や VS Code で UTF-8 no BOM の script / config / text を作る
- Windows PowerShell 5.1 や legacy tool が BOM なし file を ANSI 側の code page と見なす
- 日本語や non-ASCII を含む行だけ壊れる
ここで悪者にされがちなのは UTF-8 ですが、実際の原因は BOM なし UTF-8 を正しく推定してくれない読み手 が混ざっていることです。
flowchart TB
accTitle: UTF-8 no BOMをANSIと見なされる事故
accDescr: LinuxやVS Codeで作ったUTF-8 no BOMのfileを、Windows PowerShell 5.1やlegacy toolがANSI側のcode pageと見なして読むと、日本語などnon-ASCIIを含む行だけ壊れることを示す図。
v1["Linux / VS Codeで UTF-8 no BOMを作る"] --> v2["5.1やlegacy toolが読む"]
v2 --> v3["BOMなしfileをANSI側と見なす"]
v3 --> v4["non-ASCIIを含む行だけ壊れる"]
v3 -.-> n1["原因は推定しない読み手"]
図11: 逆方向の事故では、BOMなしUTF-8を推定できない読み手が原因になる。
4.3 Windows 側が UTF-16LE を書き、Linux 側では「テキストらしく見えない」
これもかなりあります。
- Windows PowerShell 5.1 の一部出力や legacy tool が UTF-16LE を書く
- Linux 側の text tool は UTF-8 の 1 byte stream を想定している
- 結果として NUL byte が大量に混ざった「バイナリっぽい text」になる
UTF-16LE 自体は悪くありません。ただし、Linux の text processing tool にそのまま流す前提とは噛み合わない場面が多いです。
flowchart TB
accTitle: UTF-16LEがLinux側でバイナリっぽく見える事故
accDescr: Windows PowerShell 5.1の一部出力やlegacy toolが書いたUTF-16LEを、UTF-8の1 byte streamを想定するLinuxのtext toolに流すと、NUL byteが大量に混ざったバイナリっぽいtextに見えることを示す図。
u1["5.1の一部出力や legacy tool"] --> u2["UTF-16LEを書く"]
u2 --> u3["Linuxのtext toolは 1 byte streamを想定"]
u3 --> u4["NUL byteが混ざり バイナリっぽく見える"]
図12: UTF-16LE自体は悪くないが、Linuxのtext処理の前提とは噛み合わない。
4.4 BOM の有無でも friction が起きる
BOM は encoding そのものではありませんが、実務ではかなり効きます。
- Windows 側の一部 tool は BOM があると助かる
- Linux 側の一部 tool は BOM を先頭の余計な bytes として扱う
- 結果として 1 列目や 1 行目の先頭だけ壊れる、見えないゴミが付く、比較結果がずれる
特に UTF-8 では、同じ UTF-8 でも BOM あり / なしで bytes は別物です。「UTF-8 にした」だけでは、運用ルールとしてまだ半分しか決まっていません。
flowchart TB
accTitle: BOMの有無で起きるfriction
accDescr: 同じUTF-8でもBOMあり・なしでバイト列は別物で、Windows側の一部toolはBOMがあると助かる一方、Linux側の一部toolはBOMを先頭の余計なbytesとして扱うため、先頭だけ壊れるなどのfrictionが起きることを示す図。
b0["同じUTF-8でも BOMあり / なしは別bytes"] --> w1["Windows側の一部tool"]
b0 --> l1["Linux側の一部tool"]
w1 --> w2["BOMがあると助かる"]
l1 --> l2["先頭の余計なbytes扱い"]
l2 --> l3["先頭だけ壊れる・ 見えないゴミが付く"]
図13: 「UTF-8にした」だけでは足りず、BOMの有無まで決めて初めてルールになる。
4.5 console の見え方を信じると迷う
Windows と Linux をまたぐとき、もう 1 つ危ないのが console です。
- Windows console には input / output の code page がある
- Linux terminal 側は UTF-8 locale 前提で動くことが多い
- WSL、SSH、container、CI を経由すると表示経路が増える
この状態で「console では読めたから file も大丈夫」「console では崩れたから file が壊れている」と判断すると外しやすいです。見えているものが壊れているのか、保存されている bytes が壊れているのかは、別に確認したほうが安全です。
4.6 典型事故を表にするとこうなる
| 場面 | 実際の bytes | 読み手の想定 | 典型症状 |
|---|---|---|---|
| Windows の legacy app が保存した CSV | CP932 | Linux 側は UTF-8 | �、decode error、意味不明な日本語 |
| Linux / VS Code で作った file | UTF-8 no BOM | Windows PowerShell 5.1 が ANSI 扱い | 日本語行だけ壊れる |
| Windows PowerShell 5.1 の一部出力 | UTF-16LE または ANSI | Linux 側は UTF-8 text を期待 | NUL byte 混入、バイナリっぽい挙動 |
| UTF-8 with BOM の file | UTF-8 + BOM | Unix 系 tool は plain UTF-8 前提 | 先頭列だけ壊れる、余計な文字が付く |
| console 表示だけを信じる | file と console で別前提 | 調査者が表示だけで判断 | 原因切り分けを外す |
5. 文字化け調査はこの 4 問で進める
文字化け調査で迷ったら、次の 4 問に戻るのがいちばん早いです。
5.1 元の bytes は何か
最初に見るべきは「今この file が何 bytes か」です。見た目ではなく、bytes を見る意識が必要です。
- UTF-8 か
- UTF-8 with BOM か
- CP932 か
- UTF-16LE か
- 途中で再保存されて別物になっていないか
5.2 最初に誰が、どの前提で書いたか
次に、「最初の書き手」を特定します。
- Windows の legacy app か
- PowerShell 5.1 か 7 か
- Linux の script か
- VS Code か
- Excel 由来の export か
- 何らかの middleware / batch / CI か
ここが曖昧なままだと、encoding 推定が運になります。
5.3 いま誰が、どの前提で読んでいるか
書き手だけでなく、読み手の前提も必要です。
- editor が auto-detect しているのか
- PowerShell が BOM を見ているのか
- Linux 側が locale に従って UTF-8 扱いしているのか
- library が既定 encoding を使っているのか
- 明示的に
Encoding.UTF8やcp932を指定しているのか
文字化けは、ほぼここで発生します。
5.4 読み間違えた内容が、すでに保存されたか
最後に、被害が表示だけで止まっているかを確認します。
- まだ bytes は元のままか
- 壊れて見えている内容を誰かが保存したか
?や�が差分に入っていないか- 全文が別 encoding で書き直されていないか
この 4 問が埋まれば、たいてい原因は見えます。
flowchart TB
accTitle: 文字化け調査の4問
accDescr: 元のbytesは何か、最初に誰がどの前提で書いたか、いま誰がどの前提で読んでいるか、読み間違えた内容がすでに保存されたかの4問を順に埋めれば、たいてい文字化けの原因が見えることを示す図。
q1["問1: 元のbytesは何か"] --> q2["問2: 最初に誰が どの前提で書いたか"]
q2 --> q3["問3: いま誰が どの前提で読んでいるか"]
q3 --> q4["問4: 誤読した内容は すでに保存されたか"]
q4 --> r1["原因が見える"]
図14: 調査で迷ったら、この4問に戻って順に埋めるのがいちばん早い。
6. 壊れた file をどう直すか
原因が見えたら、次は復旧です。ここで最初に決めるのは、元の bytes がまだ残っているかです。
- 元の bytes が残っている: 正しい encoding で読み直し、目的の encoding で書き出せば戻せます。この章の手順です
- 誤読した結果をすでに保存してしまった: バックアップか Git の履歴から戻すしかありません。置換文字
�や?になった文字は、正しい encoding が分かっても復元できません
そのため、いちばん最初にやるべきは 作業対象のコピーを取ること です。変換はコピーに対して行い、元 file は触らないでください。
flowchart TB
accTitle: 復旧の最初の分岐
accDescr: 復旧ではまず元のbytesが残っているかを確認し、残っていれば正しいencodingで読み直して書き出し、誤読した結果をすでに保存してしまっていればバックアップやGitの履歴から戻すしかないこと、作業はコピーに対して行うことを示す図。
s1["まずコピーを取る"] --> j1{"元のbytesは 残っているか"}
j1 -->|"残っている"| r1["正しいencodingで 読み直して書き出す"]
j1 -->|"保存済みで喪失"| r2["バックアップか Gitの履歴から戻す"]
r2 -.-> n1["置換文字になった 文字は復元できない"]
図15: 復旧はコピーを取ってから、元のbytesが残っているかどうかで道が分かれる。
6.1 Linux 側にいるなら iconv
CP932 の file を UTF-8 にするなら、iconv がいちばん素直です。
# CP932 -> UTF-8
iconv -f CP932 -t UTF-8 input.csv > output.csv
変換元の encoding を間違えていると、次のように途中で止まります。
iconv: illegal input sequence at position 0
止まること自体が「その encoding ではない」という情報 なので、候補を変えて試すのが早いです。逆に、何を渡しても止まらない場合は、その file が ASCII だけでできている可能性があります。
UTF-16LE から UTF-8 へ落とすときは -f UTF-16LE を使います。ただし、BOM 付きの file を -f UTF-16LE と明示して変換すると、BOM が U+FEFF という 1 文字として出力に残ることがあります。BOM の扱いを iconv 側に任せたいなら -f UTF-16 を使い、変換後は先頭の 1 文字を必ず確認してください。
6.2 Windows 側にいるなら PowerShell
PowerShell 6.2 以降では、-Encoding に code page 番号 を直接渡せます。CP932 は 932 です。
# PowerShell 6.2 以降。CP932 -> UTF-8 no BOM
Get-Content -Path .\input.csv -Encoding 932 |
Set-Content -Path .\output.csv -Encoding utf8NoBOM
逆に、Linux 側で作った UTF-8 の file を、CP932 しか読めない相手へ渡すならこうです。
Get-Content -Path .\input.csv -Encoding utf8 |
Set-Content -Path .\output.csv -Encoding 932
ただし、この書き方には副作用が 2 つあります。
Get-Contentは行単位に分割し、Set-Contentは各行のあとに改行を付け直します。つまり 改行コードが揃えられてしまいます- 元 file の末尾に改行が無くても、出力には付きます
改行まで含めて bytes を保ちたいなら、行に分けずに丸ごと扱います。
# 改行をそのまま保ったまま CP932 -> UTF-8 no BOM
$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) は、Windows PowerShell 5.1 ならそのまま使えます。PowerShell 7 でここが例外になる環境では、先に次を 1 回実行してから使ってください。
[System.Text.Encoding]::RegisterProvider(
[System.Text.CodePagesEncodingProvider]::Instance)
BOM を付けるかどうかは、7.1 のとおり相手都合で決めます。上の例で UTF8Encoding の引数を $true にすれば BOM 付きになります。
6.3 変換したあとに必ず見るところ
変換は「エラーが出なかった」だけでは終わりません。最低でも次を見ます。
- 代表的な日本語行を 2〜3 個、正しく読めるか
?や�が増えていないか。増えているなら、その文字はもう失われています- 行数が変わっていないか
- BOM と改行コードが、渡す相手の想定どおりか
- file サイズが極端に変わっていないか。CP932 から UTF-8 にすると日本語部分は 2 bytes から 3 bytes へ増えるので、少し大きくなるのが正常です
とくに 2 番目が重要です。CP932 に無い文字を含む UTF-8 を CP932 へ落とすときは、必ず情報が落ちます。2.2 で書いたとおり、往復して元に戻るかどうかまで確認してください。
flowchart TB
accTitle: 変換後に必ず見るところ
accDescr: 変換はエラーが出なかっただけでは終わらず、代表的な日本語行が読めるか、?や置換文字が増えていないか、行数やBOM・改行・fileサイズが想定どおりかを確認し、置換文字が増えていたらその文字はもう失われていることを示す図。
c1["変換がエラーなく終わる"] --> c2["代表的な日本語行を 2〜3個読んで確認"]
c2 --> c3["?や置換文字が 増えていないか"]
c3 --> c4["行数・BOM・改行・ サイズを確認"]
c3 -.-> n1["増えていたら その文字は失われている"]
図16: 変換は「エラーが出なかった」では終わらず、中身の確認までがワンセット。
6.4 一括変換は「移行タスク」として切り出す
最後に運用の話です。壊れた 1 本を直すのと、リポジトリ全体を CP932 から UTF-8 へ寄せるのは、別の作業です。後者は次章の 7.2 のとおり、日常の機能修正のついでにやらず、独立した移行タスクとして計画してください。
7. 事故を減らす運用ルール
ここからは実務寄りの話です。Windows と Linux をまたぐ案件では、次のルールを最初に決めておくとかなり事故が減ります。
7.1 新規 file は UTF-8 を第一候補にする
新規の text file は、まず UTF-8 を第一候補にするのが無難です。ただし、ここで止まってはいけません。BOM をどうするかも含めて決める必要があります。
おすすめはこういう決め方です。
- Linux 側で読むことが多い text: UTF-8 no BOM を基本にする
- Windows の legacy tool や Windows PowerShell 5.1 が読む script: BOM 有無を相手都合で明示する
- UTF-16LE が必要な明確な相手がいるなら、その要件を仕様として書く
「UTF-8 に統一」とだけ書くと、あとで BOM で揉めます。
flowchart TB
accTitle: 新規fileのencodingの決め方
accDescr: 新規のtext fileはUTF-8を第一候補にしたうえで止まらず、Linux側で読むことが多いならUTF-8 no BOMを基本にし、legacy toolやWindows PowerShell 5.1が読むならBOM有無を相手都合で明示し、UTF-16LEが必要な相手がいるなら要件を仕様として書くところまで決めることを示す図。
s1["新規のtext file"] --> s2["UTF-8を第一候補にする"]
s2 --> j1{"誰が読むか"}
j1 -->|"Linux側が多い"| r1["UTF-8 no BOMを基本に"]
j1 -->|"5.1やlegacy tool"| r2["BOM有無を相手都合で明示"]
j1 -->|"UTF-16LEが必要な相手"| r3["要件を仕様として書く"]
図17: 新規fileはUTF-8を第一候補にしたうえで、BOMの扱いまで相手都合で決め切る。
7.2 既存 legacy file は、明示的な移行タスクまで維持する
既存 file が CP932 なら、日常の機能修正のついでに勝手に UTF-8 化しないほうが安全です。
運用としてはこの形が安全です。
- 既存 file は、元の encoding / BOM / 改行を維持する
- encoding 変更は、移行タスクとして分離する
- 変換対象、影響範囲、下流 consumer を確認してから一括変換する
文字化け事故の多くは、善意の「ついで UTF-8 化」から始まります。
7.3 encoding を interface の一部として扱う
CSV、TXT、log、設定 file、簡易 protocol は、内容だけでなく encoding 自体が interface です。
たとえば、仕様として最低限ここまでは書きたいです。
- この file は UTF-8 / CP932 / UTF-16LE のどれか
- UTF-8 の場合 BOM は付くか
- 改行は LF / CRLF のどちらか
- Linux / Windows のどちらが producer / consumer か
- 途中の batch や ETL が再保存しないか
「text で渡す」は仕様になっていません。
7.4 既定値を信用せず、書き込み時は明示する
code 上でも script 上でも、encoding は明示したほうが安全です。
危ないのは、こんな考え方です。
- 既定のまま保存する
- OS に合わせてたぶんいい感じになると思う
- console で読めたから file も大丈夫だろう
- auto-detect があるから大丈夫
既定値は、Windows / Linux、PowerShell 5.1 / 7、editor、runtime で普通に変わります。明示しない限り、たまたま動いているだけになりやすいです。
7.5 console と file を分けて確認する
地味によく効くのがこのルールです。
- console での表示確認
- file を再オープンしての確認
この 2 つを分けます。
chcp や terminal の表示が合っていても、保存 file が別 encoding なら意味がありません。逆に file は正常でも、console の表示 code page が合っていなければ見た目だけ壊れます。
7.6 Git は encoding を直してくれない
地味ですが大事です。
Git は基本的に bytes を追跡するだけです。つまり、壊れた bytes も、そのまま真面目に履歴へ入れます。
そのため、
- 何も変えていないのに巨大差分が出た
- 日本語の行だけ謎差分になった
- 先頭行だけ変わった
- 改行と encoding が一緒に変わった
というときは、内容変更より先に re-encoding の事故を疑ったほうがいいです。
flowchart TB
accTitle: Gitはencodingを直してくれない
accDescr: Gitは基本的にbytesを追跡するだけで、壊れたbytesもそのまま履歴に入るため、何も変えていないのに巨大差分が出る、日本語の行だけ謎差分になるといったときは、内容変更より先にre-encodingの事故を疑うべきことを示す図。
g1["Gitはbytesを 追跡するだけ"] --> g2["壊れたbytesも そのまま履歴に入る"]
g2 --> g3["巨大差分・日本語行 だけの謎差分が出る"]
g3 --> g4["内容変更より先に re-encoding事故を疑う"]
図18: Gitは壊れたbytesも真面目に記録するので、謎差分はまずre-encodingを疑う。
8. 最低限のチェックリスト
Windows と Linux が混ざる案件で、最初に固定したいチェックリストを置いておきます。
flowchart TB
accTitle: チェックリストの流れ
accDescr: 編集前に現在のencodingやBOM・改行を確認し、編集中は既定値やauto-detect任せを避け、編集後に再オープンと差分の確認をする流れと、一括変換などは移行タスクとして別に扱うことを示す図。
c1["編集前: 現在のencoding・ BOM・改行を確認"] --> c2["編集中: 既定値や auto-detect任せを避ける"]
c2 --> c3["編集後: 再オープンと 差分を確認"]
c3 -.-> t1["一括変換などは 移行タスクとして分離"]
図19: チェックは編集前・編集中・編集後で分け、一括変換は別タスクに切り出す。
8.1 編集前
- この file の現在の encoding は何か
- BOM はあるか
- 改行は LF / CRLF のどちらか
- 代表的な日本語行を 2〜3 個メモしたか
- Linux 側 / Windows 側のどちらが最終 consumer か分かっているか
8.2 編集中
- 既定 encoding に依存した書き込みをしていないか
- auto-detect 任せで save していないか
- PowerShell や shell redirection の経路を雑に使っていないか
- 「表示が読める」だけで安心していないか
8.3 編集後
- 保存後に再オープンして確認したか
- Linux 側でも Windows 側でも代表行が崩れていないか
?や�が差分に増えていないか- 先頭行や先頭列だけ壊れていないか
- BOM / 改行だけの大差分になっていないか
8.4 移行タスクとしてやるべきもの
- CP932 → UTF-8 の一括変換
- UTF-8 BOM policy の統一
- PowerShell 5.1 前提 script の棚卸し
- CI / container / WSL / SSH 経由の text pass の明文化
- editor / formatter / batch の保存設定の統一
9. まとめ
Windows の文字コード問題を一言で言うなら、Unicode の世界と legacy code page の世界が、いまも同居していることが本質です。
そして Linux と組み合わせたときに事故が増えるのは、Linux 側が UTF-8 前提で流れることが多く、Windows 側の CP932 や UTF-16、console code page、PowerShell の版差が一気に表に出るからです。
覚えておきたいのは次の 5 点です。
- 文字化けは bytes の解釈ずれ
- 表示崩れとデータ破損は別
- Windows では file / editor / console / API の層を分けて考える
- Linux とやり取りする text は UTF-8 を第一候補にする
- 既存 legacy file の変換は、通常改修と分ける
「Windows で文字化けした」をそのまま扱うと、話が広すぎます。でも、
- 元の bytes は何か
- 誰がどう書いたか
- 誰がどう読んだか
- すでに保存されたか
の 4 問で切れば、かなり整理できます。
文字コードは地味ですが、Windows と Linux の間では I/O 契約そのものです。ここを曖昧にしないことが、いちばん効く対策です。
10. 参考資料
Windows / Microsoft
- Code Pages - Win32 apps | Microsoft Learn
- Code Page Identifiers - Win32 apps | Microsoft Learn
- Unicode in the Windows API - Win32 apps | Microsoft Learn
- Console Code Pages - Windows Console | Microsoft Learn
- chcp | Microsoft Learn
- Use UTF-8 code pages in Windows apps | Microsoft Learn
PowerShell / VS Code
- about_Character_Encoding | Microsoft Learn
- Understanding file encoding in VS Code and PowerShell | Microsoft Learn
GNU / Linux locale
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windowsの文字コードと改行コード - 文字化けとCRLF/LFの基本
Windows で混ざりやすい Shift_JIS / UTF-8 / UTF-16、文字化け、CRLF / LF の違いを、実務で判断しやすい形に整理します。
WindowsでCodexの文字化け事故を減らす指示ルール
Windows で Codex に日本語ファイルを扱わせるとき、推測保存を避け、既存 encoding を維持し、再読込で検証するための実務的な指示ルールを整理します。
DSCでWindowsを宣言的に構成管理する ── dsc.exeで始めるIaC
「もう一度実行すると壊れる」手順書スクリプトから卒業しませんか。Windowsの構成をYAMLで宣言し冪等に適用するDSC v3(dsc.exe)を、4世代あるDSCの整理から構成ドキュメントの書き方、ドリフト検出の運用まで解説します。
日本語フォントと文字の落とし穴 ── JIS2004・異体字セレクタ・外字を業務アプリでどう扱うか
「画面と帳票で葛の字の形が違う」「氏名の字が出ない」──業務システムの文字トラブルは、文字コード(データ)とフォント(見た目)の層を分ければ整理できます。JIS2004の字形変更、異体字セレクタ、外字の実務対応を解説します。
WMI/CIMをC#・PowerShellから使う ── ハードウェア情報取得・プロセス監視・リモート照会の実務ガイド
PCのシリアル番号取得、ディスク空き監視、プロセス起動検知の定番がWMI/CIMです。Get-CimInstance等のCIMコマンドレットの使い方と旧Get-WmiObjectからの移行、C#のSystem.ManagementとCIM APIの使い分け、実例レシピと落と...
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
技術相談・設計レビュー
Windows と Linux の間で CSV、ログ、設定ファイルの文字コード前提がずれる案件では、I/O 契約と運用ルールを先に整理すると事故を減らしやすいです。
Windowsアプリ開発
Windows 向け業務ツールでは、CP932 と UTF-8 が混在する現場が多く、文字コードの扱いを設計に入れておくことが保守性に直結します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- Windowsで文字化けが起きる原因は何ですか?
- ほとんどは、同じバイト列を別の文字コードとして読んだか、誤って読んだ結果を別の文字コードで保存したことが原因です。たとえば「あ」をUTF-8で保存したバイト列(E3 81 82)をCP932の文脈で読むと「縺」のような別の文字列に見えます。日本語が難しいから起きるのではなく、encodeとdecodeの前提が一致していないことが正体です。
- 文字化けしたファイルは元に戻せますか?
- 元のバイト列が変わっていなければ、正しいencodingで開き直すことで戻せる場合があります。危ないのは、誤読して壊れて見えている内容をそのまま保存してしまう流れで、この段階に入ると表示崩れではなくデータ破損になります。また、Unicode文字列をCP932のような狭いcode pageに落として「?」や置換文字に変わった場合、一度失われた文字はあとから正しいencodingを知っても復元できません。
- chcp 65001にすればファイルもUTF-8になりますか?
- なりません。consoleのcode pageを変えることと、既存ファイルのバイト列が何かは別問題です。Windowsでは、ファイル自体の文字コード、エディタの解釈、consoleのinput/output code page、アプリ内部の文字列形式は別物で、この層を分けて考える必要があります。consoleで読めたからファイルも大丈夫、という判断は外しやすいため、console表示の確認とファイルを再オープンしての確認は分けるべきです。
- WindowsとLinuxでテキストをやり取りするときの安全なルールは?
- 新規ファイルはUTF-8を第一候補にし、BOMの扱いまで決めることです。Linux側で読むことが多いテキストはUTF-8 no BOMを基本にし、Windows PowerShell 5.1などのlegacyツールが読む場合はBOM有無を相手都合で明示します。既存のCP932ファイルは日常改修のついでに変換せず、明示的な移行タスクとして分離します。また、CSVやログはencoding自体がinterfaceなので、encoding・BOM・改行コードを仕様として書いておくと事故が減ります。