釐清 Windows 的文字編碼 - 亂碼為什麼會發生,尤其是與 Linux 搭配時什麼地方會偏掉

· · Windows, 亂碼, UTF-8, CP932, Linux, PowerShell, Unicode

Windows 的亂碼,並不是因為日文難處理才發生。絕大多數是因為 把同一串 bytes 當成另一種編碼去讀,或 把錯誤讀到的結果又以另一種編碼存回去

尤其當 Windows 與 Linux 同時參與時,Windows 側還並存 CP932、UTF-8、UTF-16、console 的 code page、PowerShell 各版本差異等等多重脈絡,而 Linux 側多半以 UTF-8 為前提,於是平時沒顯現的前提差異就會一次全部浮上檯面。

這其實不是「日文處理難」的問題,而是 同一組 bytes 是用什麼前提在處理 的問題。本文就從「亂碼為什麼會發生」的角度整理 Windows 的文字編碼,再把與 Linux 搭配時特別容易出事的點聚焦一下。

1. 先抓住的重點

先把要點列出來,最重要的是下列 6 項:

  • 亂碼不是「文字」的問題,是「bytes 被怎麼解釋」的問題
  • Windows 上 Unicode 系與 legacy code page 系並存,同一台機器裡不同脈絡的前提就不同。
  • Linux 側以 UTF-8 為前提居多,所以摻進 Windows 側的 CP932 或 UTF-16 就容易出事。
  • 只是顯示壞掉把壞掉的內容也存回去 要分開想。
  • 新檔以 UTF-8 為第一首選既有 legacy 檔在有明確遷移任務之前先維持原狀 是最安全的。
  • 檔案的 encoding、編輯器的 encoding、console 的 code page、應用內部的字串格式是不同層。混在一起講,調查就會迷路。

單說「在 Windows 上亂碼了」無法定位原因。至少要分清楚下列哪一層偏掉:

  • 檔案本身的編碼
  • 儲存時的編碼
  • 編輯器的解讀
  • console 的 input/output code page
  • 應用程式內部的字串格式
  • Linux 側的 locale 與預期編碼

2. 亂碼的本質

亂碼的本質其實很單純:

  1. 把字串以某種編碼 encode 成 bytes
  2. 再把那串 bytes 以某種編碼 decode 回字串
  3. encode 與 decode 的前提不一致,就會讀成不同的字串

舉例: 以 UTF-8 儲存,bytes 是:

E3 81 82

把這串 bytes 當 UTF-8 讀是 ,用 CP932 脈絡讀就會變成 縺� 這樣的另一個字串。這就是亂碼。

重點是:這裡發生的不是「日文壞了」,而是 同一組 bytes 的解釋錯位

2.1 只是顯示壞掉還有機會救

亂碼其實有「還救得回來」的階段。只要 原本的 bytes 沒被改動,之後用正確的編碼重新開就有機會還原。

反過來危險的流程是這樣:

  1. 把 UTF-8 檔案誤當 CP932 讀
  2. 畫面上看起來像 縺�
  3. 就這樣把「看到的字串」存起來
  4. 原本的 UTF-8 bytes 被覆蓋掉

走到這一步,就不是顯示問題,而是 資料毀損

2.2 更危險的,是把「表現不出的字」塞進窄 code page

另一種典型事故,是把 Unicode 字串往 CP932 這種 legacy code page 寫。

若對方 code page 裡沒有那個字,會出現:

  • 被換成 ?
  • 被塞
  • 被換成相近的別字
  • 轉換失敗

這種事故要看的不只是「讀不讀得到」,而是 來回轉一次還能還原嗎。一旦失掉的字元,後面就算知道正確編碼也還不回來。

3. 為什麼 Windows 上容易變複雜

Windows 之所以複雜,不是因為它「老」,而是 Unicode 的世界與 legacy code page 的世界至今仍共存

3.1 Windows API 同時有 Unicode 系與 code page 系

Windows API 大致分兩套:

  • W 系:wide character,以 UTF-16 處理 Unicode
  • A 系:ANSI,走當時的 active code page

也就是說,Windows 本身就同時存在「以 Unicode 處理的路」與「以當下 active code page 處理的路」。所以在同一台 Windows 上,走哪條 API 或哪個工具,前提就不一樣。

3.2 「Windows 的日文」不只一個

在 Windows 日文環境上,實務上常見混用的有 4 種:

  • CP932:日文 Windows 的 legacy 文字常見
  • UTF-8:新的文字資產、web、跨平台工具上越來越多
  • UTF-16LE:Windows 系工具或 API 脈絡中仍然常見
  • console 的 code pagecmd.exe 或一部分 console 工具在輸入/輸出時的另一層

要注意:chcp 65001 不會讓檔案也變成 UTF-8。console 的 code page 和既有檔案是什麼 bytes,是兩件事。

另外,把日文 Windows 的 legacy 文字粗略叫「Shift_JIS」的人很多,但實務上用 CP932 這個名字比較不會聊歪,至少明確在講「這是 Windows 家的日文 legacy 編碼」。

3.3 檔名與檔案內容是不同問題

Windows 上日文檔名看起來沒事,很容易就誤以為「內容也沒事」。這就是陷阱。

  • 處理 path/file name 的層
  • 讀檔案內容的層
  • 顯示到 console 的層

這 3 個層要分開看。

例如日文 path 完全沒問題,但檔案內容是 CP932 存的,被 Linux 當 UTF-8 讀就炸。反之,檔案內容是 UTF-8,console 的 code page 不對也只是顯示壞。

3.4 PowerShell 與周邊工具的預設值並不一致

讓 Windows 低調地製造事故的一個原因是:「我覺得我只是輸出文字」,但走不同路徑輸出的 bytes 卻不同

特別要留意:

  • Windows PowerShell 5.1 的預設 encoding 不一致
  • 部分 cmdlet 或重新導向會產生 UTF-16LE
  • 另一些路徑則使用 active ANSI code page
  • PowerShell 7 之後 預設為 UTF-8 no BOM

也就是說,「我是用 PowerShell 輸出的文字」本身還定不了編碼。要看是哪一版、哪個 cmdlet、走哪條寫檔路徑

4. 與 Linux 搭配時的典型事故

Windows 單機原本看起來還好的東西,一旦夾進 Linux,就變得很容易壞。理由簡單,Linux 側 以 UTF-8 為前提的傾向很強

4.1 Windows 以 CP932 存的檔,被 Linux 當 UTF-8 讀

最常見的事故:

  • Windows 上 legacy 應用或老舊流程用 CP932 寫 CSV/TXT/log
  • Linux 側的 script 或工具依 locale 預設當 UTF-8 讀
  • 結果就是 decode error、、意義不明的日文

這時不是 Linux 工具不好,而是 這串 bytes 根本沒附上編碼這個約定

4.2 Linux/VS Code 做的 UTF-8 no BOM 被 Windows 當 ANSI

反方向的事故:

  • 在 Linux 或 VS Code 做的 UTF-8 no BOM 的 script/config/文字
  • Windows PowerShell 5.1 或 legacy 工具把沒 BOM 的檔當成 ANSI 側的 code page
  • 只有含日文或非 ASCII 的那幾行壞掉

這個事故不是 UTF-8 本身的錯,而是 混進了「沒 BOM 時也猜不對 UTF-8」的讀檔方

4.3 Windows 寫 UTF-16LE,Linux 看到「這個不像文字」

這種也常見:

  • Windows PowerShell 5.1 的部分輸出或 legacy 工具寫出 UTF-16LE
  • Linux 側的文字工具預期是 UTF-8 的單 byte 流
  • 結果變成大量 NUL byte 夾在裡面的「像是二進位的文字」

UTF-16LE 本身沒罪,但 直接丟給 Linux 的文字處理工具這個前提常常不合拍

4.4 BOM 有無也會帶來摩擦

BOM 不是編碼本身,但在實務上影響很大:

  • Windows 側某些工具有 BOM 會比較輕鬆
  • Linux 側某些工具把 BOM 視為檔首的多餘 bytes
  • 結果是第一欄或第一行的開頭壞掉、帶入看不到的垃圾、比較結果偏差

尤其在 UTF-8 上,同樣叫 UTF-8,有 BOM 與沒 BOM 的 bytes 完全不同。只寫「改用 UTF-8」等於把規則定了一半。

4.5 光信 console 會誤判

Windows 與 Linux 跨系統時,另一個危險區是 console:

  • Windows console 有 input/output 的 code page
  • Linux terminal 多半在 UTF-8 locale 下跑
  • 中間還要經 WSL、SSH、container、CI,顯示路徑又多了一層

在這種狀況下以「console 能讀 → 檔案也 OK」或「console 壞掉 → 檔案壞了」去判斷很容易偏掉。看到的東西壞掉,和實際存下來的 bytes 壞掉,要分開驗

4.6 典型事故整理成表

情境 實際的 bytes 讀檔方預期 典型症狀
Windows legacy app 存的 CSV CP932 Linux 側是 UTF-8 、decode error、看不懂的日文
Linux/VS Code 做的檔 UTF-8 no BOM Windows PowerShell 5.1 當 ANSI 只有日文行壞掉
Windows PowerShell 5.1 某些輸出 UTF-16LE 或 ANSI Linux 側預期是 UTF-8 文字 NUL byte 混入、像二進位的行為
UTF-8 with BOM UTF-8 + BOM Unix 系工具預期 plain UTF-8 首欄壞掉、帶入多餘字元
只信 console 的顯示 檔案與 console 前提不同 調查者只看顯示判斷 切分原因時走偏

5. 以 4 個問題推進亂碼調查

調查亂碼時若卡住,回到下面這 4 題最有效率。

5.1 原本的 bytes 是什麼

最先要看的是「現在這個檔到底是什麼 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 的匯出
  • 某個 middleware/batch/CI

這層不釐清,推測編碼就變碰運氣。

5.3 現在是誰、用什麼前提讀的

除了寫的人,也要看 讀的人的前提

  • 編輯器是否在自動偵測
  • PowerShell 看不看 BOM
  • Linux 是否依 locale 當 UTF-8 處理
  • library 是否走預設編碼
  • 是否有明確指定 Encoding.UTF8cp932

亂碼幾乎都發生在這裡。

5.4 讀錯的內容是否已經被存起來

最後確認 損害是否還停在「只有顯示」這一階段

  • bytes 還是原本的嗎
  • 有沒有人把壞掉的顯示存回去
  • diff 裡有沒有冒出 ?
  • 整檔有沒有被改成別的編碼

4 題補齊之後,原因通常就很清楚。

6. 減少事故的運維規則

以下是實務面的東西。Windows 與 Linux 同時參與的案子,先把下列規則定好能省很多事故。

6.1 新檔以 UTF-8 為第一首選

新檔的 text 檔以 UTF-8 為起點最穩。但別停在這裡,BOM 的走法也要一起決定

建議思路:

  • 主要被 Linux 側讀的 text:採 UTF-8 no BOM
  • 給 Windows legacy 工具或 PowerShell 5.1 讀的 script:依對方情況明確指定 BOM 有無
  • 確定需要 UTF-16LE 的對象:在規格書裡寫清楚

只寫「統一 UTF-8」之後一定為了 BOM 吵架。

6.2 既有 legacy 檔在明確遷移任務之前維持原狀

既有檔若是 CP932,別趁日常修 bug 順手轉 UTF-8 比較安全。

穩當的做法:

  • 既有檔 維持原 encoding/BOM/換行
  • 編碼變更 獨立成遷移任務
  • 轉換對象、影響範圍、下游 consumer 都確認後才統一轉換

亂碼事故很多都是從好心的「順手轉 UTF-8」開始的。

6.3 把編碼當成介面的一部分

CSV、TXT、log、設定檔、簡易 protocol,不只是內容,編碼本身就是介面

至少要在規格裡寫下面這些:

  • 這個檔是 UTF-8/CP932/UTF-16LE 的哪一個
  • 若是 UTF-8,有沒有 BOM
  • 換行是 LF 還是 CRLF
  • 誰是 producer、誰是 consumer(Linux/Windows)
  • 中間的 batch 或 ETL 是否會再存一次

只寫「以 text 傳遞」不算規格。

6.4 別信預設值,寫入時明確指定

不論程式碼還是 script,寫入時把編碼寫明最安全。

危險的想法:

  • 用預設儲存就好
  • OS 自己會處理吧
  • console 讀得到檔也應該沒事
  • 反正有 auto-detect

預設值在 Windows/Linux、PowerShell 5.1/7、編輯器、runtime 間會直接差異化。不明確指定,多半就只是剛好能動

6.5 把 console 與檔案分開驗

這條相當有效:

  • 驗 console 顯示
  • 重新打開檔案驗一次

兩個步驟要分開。

chcp 或 terminal 的顯示對得上,但存檔是另一種編碼就沒用;反過來,檔案正常但 console 的顯示 code page 不對,你也會被唬。

6.6 Git 不會幫你修編碼

Git 基本上 只追 bytes。也就是說,壞掉的 bytes 也會被忠實記錄。

所以遇到:

  • 什麼都沒改卻冒出超大 diff
  • 只有日文行出現怪 diff
  • 只有檔首壞掉
  • 換行與編碼一起動

這類情況,先懷疑 重新編碼造成的事故 比懷疑內容改動更快對味。

7. 最低限度的 checklist

Windows 與 Linux 混合的案子,一開始就先固定下列 checklist:

7.1 編輯前

  • 這個檔目前是什麼編碼
  • 有沒有 BOM
  • 換行是 LF 還是 CRLF
  • 代表性的日文行記 2〜3 條
  • 最終 consumer 是 Linux 還是 Windows

7.2 編輯中

  • 是否依賴預設編碼在寫檔
  • 是不是靠 auto-detect 在 save
  • PowerShell 或 shell redirection 的路徑是不是隨便用
  • 「顯示能讀」就放心,這種心態是不是冒出來了

7.3 編輯後

  • 存完後重新開來驗了嗎
  • Linux 與 Windows 兩邊的代表行都沒壞吧
  • diff 有沒有多出 ?
  • 是不是只壞首行或首欄
  • 是不是只有 BOM/換行的大 diff

7.4 該做為遷移任務處理的

  • CP932 → UTF-8 的統一轉換
  • UTF-8 BOM policy 的統一
  • 以 PowerShell 5.1 為前提的 script 盤點
  • CI/container/WSL/SSH 經過的 text 通道書面化
  • editor/formatter/batch 的儲存設定統一

8. 總結

要用一句話說 Windows 的文字編碼問題:Unicode 的世界與 legacy code page 的世界,在今天仍舊共存

而與 Linux 搭配時會特別容易出事,是因為 Linux 側常以 UTF-8 為前提,Windows 側的 CP932、UTF-16、console code page、PowerShell 版本差異一次被攤出來。

要記住的 5 點:

  • 亂碼是 bytes 解釋錯位
  • 顯示壞掉與資料毀損是兩件事
  • 在 Windows 上把檔案/editor/console/API 分層思考
  • 與 Linux 交換的 text 以 UTF-8 為第一首選
  • 既有 legacy 檔的轉換要和一般修改分開

光說「Windows 上亂碼」範圍太大,但換成:

  • 原本的 bytes 是什麼
  • 誰怎麼寫
  • 誰怎麼讀
  • 是否已經存下去了

這 4 題一切,就能收斂很多。

文字編碼看似瑣碎,但在 Windows 與 Linux 之間它就是 I/O 契約本身。把這裡釘清楚,是最有效的對策。

9. 參考資料

Windows / Microsoft

  • [Code Pages - Win32 apps Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/intl/code-pages)
  • [Code Page Identifiers - Win32 apps Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers)
  • [Unicode in the Windows API - Win32 apps Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/intl/unicode-in-the-windows-api)
  • [Console Code Pages - Windows Console Microsoft Learn](https://learn.microsoft.com/en-us/windows/console/console-code-pages)
  • [chcp Microsoft Learn](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/chcp)
  • [Use UTF-8 code pages in Windows apps Microsoft Learn](https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page)

PowerShell / VS Code

  • [about_Character_Encoding Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding)
  • [Understanding file encoding in VS Code and PowerShell Microsoft Learn](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding)

GNU / Linux locale

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽