Windows文字コード入門 - Linux連携で起きる文字化け

· 更新日: · · Windows, 文字化け, UTF-8, CP932, Linux, PowerShell, Unicode

更新履歴(4件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(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://staging.comcomponent.com/blog/2026/03/21/000-windows-text-encoding-mojibake-linux/

DOI(最新版)
10.5281/zenodo.21589718
DOI(この版)
10.5281/zenodo.21732736

Windows の文字化けは、日本語が難しいから起きるわけではありません。ほとんどは、同じバイト列を別の文字コードとして読んだか、誤って読んだ結果を別の文字コードで保存したことが原因です。

特に Windows と Linux をまたぐと、Windows 側には CP932、UTF-8、UTF-16、console の code page、PowerShell の版差など複数の文脈が残り、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

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 では LANGLC_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の契約として明文化する運用を推奨しています。

Windowsの文字化けとLinux連携の知識マップ同じバイト列をCP932とUTF-8のどちらで読むかで文字化けの症状が変わること、BOMやUTF-16LEやコンソールのコードページが独立した層であること、PowerShellのバージョンで既定のencodingが異なること、iconvやPowerShellによる復旧とUTF-8を第一候補にする運用方針の関係を示す図原因になり得る原因になり得る原因になり得る原因になり得る原因になり得る原因になり得る利用する利用する利用する軽減する軽減するで確認できる利用する利用する原因になり得る推奨される対応推奨される対応文字化けCP932UTF-8UTF-16LEBOM(Byte Order Mark)誤読内容の上書き保存によるデータ破損コンソールのコードページWindows PowerShell 5.1PowerShell 7iconvPowerShellLinuxのlocaleWSL(Windows Subsystem for Linux)encodingをI/O契約として明文化する運用新規ファイルをUTF-8第一候補にする運用方針

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全17件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. 文字化けの正体

文字化けの正体は、かなり単純です。

  1. 文字列をどこかの文字コードで encode してバイト列にする
  2. そのバイト列をどこかの文字コードで decode して文字列に戻す
  3. encode と decode の前提が一致しなければ、別の文字列として読まれる

たとえば、 を UTF-8 で保存すると、バイト列は次になります。

E3 81 82

このバイト列を UTF-8 として読めば ですが、CP932 側の文脈で読めば 縺� のような別の文字列に見えます。これが文字化けです。

大事なのは、ここで起きているのが「日本語が壊れた」ではなく、同じ bytes に対する解釈がずれただけだという点です。

逆向きも見ておきます。 を CP932 で保存すると、バイト列はこうなります。

82 A0

このバイト列を UTF-8 として読もうとすると、0x820xA0 も 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 で開き直せば戻せる場合があります。

逆に危ないのは、次のような流れです。

  1. UTF-8 の file を CP932 として誤読する
  2. 画面上では 縺� のように見える
  3. そのまま「見えている文字列」を保存する
  4. もとの UTF-8 bytes が失われる

この段階に入ると、単なる表示崩れではなく、データ破損です。

2.2 さらに危ないのは「表現できない文字」を狭い code page に落とすとき

もう 1 つの典型事故は、Unicode 文字列を CP932 のような legacy code page に落とすときです。

たとえば、相手の code page に存在しない文字が含まれていると、

  • ? に置き換わる
  • 置換文字 が入る
  • 近い別文字に変換される
  • 変換失敗になる

といったことが起きます。

この事故は、読める・読めないだけでなく、往復変換して元に戻るかで見るべきです。一度失われた文字は、あとから正しい encoding を知っても復元できません。

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 やどのツールを通ったかで前提が変わります。

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 が何かは別問題です。

なお、日本語 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 が合っていなければ表示だけ崩れます。

層の関係を図にすると、こうなります。

書き手アプリ / エディタ / スクリプトファイルのバイト列ここだけが事実読み手A: エディタ自動判定または指定 encoding読み手B: console入出力の code page読み手C: アプリ内部ライブラリの既定 encoding読み手D: Linux 側locale に従い UTF-8 前提表示だけ崩れる保存し直すと破損に変わる表示だけ崩れるファイルは無事処理結果が壊れる下流へ伝播するdecode error か置換文字

見るべきポイントは 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-ContentAdd-Content(新規または空ファイル) ANSI = active code page。日本語環境なら CP932 UTF-8 no BOM
Export-Csv ASCII。非 ASCII は落ちます UTF-8 no BOM
Export-ClixmlNew-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-ContentANSI とみなしますが、Import-CsvSelect-StringUTF-8 とみなします。同じセッションの中で前提が割れているわけです。

ここで実務上いちばん刺さるのは、同じ「テキストを書いた」でも Out-File は UTF-16LE、Set-Content は CP932 になる という点です。4.3 で挙げた「NUL byte が大量に混ざったバイナリっぽい text」は、たいてい >Out-File の既定から来ています。

もう 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 の約束が付いていないのが根本原因です。

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 を正しく推定してくれない読み手 が混ざっていることです。

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 にそのまま流す前提とは噛み合わない場面が多いです。

4.4 BOM の有無でも friction が起きる

BOM は encoding そのものではありませんが、実務ではかなり効きます。

  • Windows 側の一部 tool は BOM があると助かる
  • Linux 側の一部 tool は BOM を先頭の余計な bytes として扱う
  • 結果として 1 列目や 1 行目の先頭だけ壊れる、見えないゴミが付く、比較結果がずれる

特に UTF-8 では、同じ UTF-8 でも BOM あり / なしで bytes は別物です。「UTF-8 にした」だけでは、運用ルールとしてまだ半分しか決まっていません。

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.UTF8cp932 を指定しているのか

文字化けは、ほぼここで発生します。

5.4 読み間違えた内容が、すでに保存されたか

最後に、被害が表示だけで止まっているかを確認します。

  • まだ bytes は元のままか
  • 壊れて見えている内容を誰かが保存したか
  • ? が差分に入っていないか
  • 全文が別 encoding で書き直されていないか

この 4 問が埋まれば、たいてい原因は見えます。

6. 壊れた file をどう直すか

原因が見えたら、次は復旧です。ここで最初に決めるのは、元の bytes がまだ残っているかです。

  • 元の bytes が残っている: 正しい encoding で読み直し、目的の encoding で書き出せば戻せます。この章の手順です
  • 誤読した結果をすでに保存してしまった: バックアップか Git の履歴から戻すしかありません。置換文字 ? になった文字は、正しい encoding が分かっても復元できません

そのため、いちばん最初にやるべきは 作業対象のコピーを取ること です。変換はコピーに対して行い、元 file は触らないでください。

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 以降では、-Encodingcode 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 つあります。

  1. Get-Content は行単位に分割し、Set-Content は各行のあとに改行を付け直します。つまり 改行コードが揃えられてしまいます
  2. 元 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 で書いたとおり、往復して元に戻るかどうかまで確認してください。

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 で揉めます。

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 の事故を疑ったほうがいいです。

8. 最低限のチェックリスト

Windows と Linux が混ざる案件で、最初に固定したいチェックリストを置いておきます。

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

PowerShell / VS Code

GNU / Linux locale

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

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・改行コードを仕様として書いておくと事故が減ります。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る