Shift_JIS 與日文的歷史包袱 — 地雷字元與 CP932
Shift_JIS 的設計讓第 2 位元組侵入 ASCII 的區域。從結構理解由此而生的「地雷字元」問題,以及半形片假名、CP932 與它至今仍在服役的原因。
一輸入「ソフト」就壞掉 — 像假的一樣的真實故障
在老舊的業務系統儲存日文單字「ソフト」(軟體),不知為何只有那一列資料崩壞。建立一個路徑含有「表」字的資料夾,特定工具就會失敗。— 在 Shift_JIS 的現場,流傳著許多這種乍看有如靈異現象的故障傳說。本章的目標,就是讓你能從結構出發說明這些現象。
Shift_JIS 是 Unicode 之前的時代,為了「在與 ASCII 共存的同時塞進日文」而打造的編碼。ASCII 維持 1 個位元組,全形字元以 2 個位元組表示。它是日文的傳統(legacy)編碼;如果你熟悉台灣的 Big5,可以把它想成在日文世界中扮演類似 Big5 角色的存在 — 同樣是 Unicode 普及之前各語言自行發展的雙位元組編碼。
問題出在第 2 位元組的範圍。0x40〜0x7E 與 ASCII 的英文字母、符號的值完全重疊。也就是說,單獨取出全形字元的後半 1 個位元組,會與 ASCII 的符號分不出來。前一章看到的 UTF-8 那種「組成多位元組字元的位元組全都在 0x80 以上」的保證,這裡並不存在。
「表」的第 2 位元組 0x5C,在 ASCII 中是反斜線。對以位元組為單位掃描的程式來說,「表」的後半看起來就是符號。
地雷字元 — 第 2 位元組 0x5C 的衝突
ASCII 的 0x5C 是反斜線 \(在日文環境常顯示成日圓符號的字元)。在許多程式語言與設定檔中,它被當作「把下一個字元特殊處理」的跳脫符號使用。
來看 Shift_JIS 的「表」。它的位元組序列是 95 5C — 第 2 位元組正好是 0x5C。不理會 Shift_JIS、以位元組為單位掃描字串的處理系統,會把 95 後面的 5C 誤解成獨立的反斜線,再把下一個位元組當成跳脫的對象吃掉。結果「表」的後半被吃掉、字元缺損,之後的解讀連鎖性地錯開 — 這就是開頭那些「靈異故障」的真面目。
第 2 位元組是 0x5C 的字元有「ソ(83 5C)」「能(94 5C)」「表(95 5C)」「十」「予」「構」等許多字,俗稱地雷字元(日文稱「ダメ文字」)。每個都是日常用語中會正常出現的字元,於是形成「只有含特定單字時才壞掉」這種重現條件難以捉摸的故障。0x5C 之外,第 2 位元組撞上 "(0x22)或 |(0x7C)等符號的組合,也可能發生同類問題。
小題 4-1 — 2 位元組結構與地雷字元
從 Shift_JIS 的結構,推導出為什麼只有特定的字元會惹出麻煩。
Q1. 關於 Shift_JIS 的結構,正確的說明是哪一個?
Shift_JIS 是在保住 ASCII 相容的同時,用 2 個位元組塞進漢字的設計,但它在第 2 位元組使用了 0x40〜0x7E 這個與 ASCII 英文字母、符號相同的值域。也就是說,光看「全形字元的後半位元組」,會與 ASCII 的符號分不出來。它沒有 UTF-8 那種「組成多位元組字元的位元組全都在 0x80 以上」的保證,這正是下一題問題的伏筆。
Q2. 在 Shift_JIS 中,日文漢字「表」是 95 5C 這 2 個位元組。把含有這個字元的字串,交給把 0x5C 當作跳脫符號(反斜線)特殊處理的處理系統,有時會壞掉。原因是哪一個?
對不認識 Shift_JIS(或以位元組為單位處理)的程式來說,95 5C 看起來是「95 這個什麼東西」加上「反斜線」。一看到反斜線,它就把下一個字元當成跳脫的一部分吃掉,於是「表」的後半被吃掉、之後的解讀連鎖性地錯開。與其說是亂碼,症狀更接近「資料破壞」— 這就是地雷字元問題可怕的地方。
Q3. 屬於所謂「地雷字元」的例子組合,正確的是哪一個?
第 2 位元組是 0x5C(反斜線)的字元是代表性的地雷字元,「ソ」「能」「表」「十」「予」等都屬於此類。這些都是公司名稱或訊息中會正常出現的字元,於是就會出現「ソフト」(軟體)這個詞造成故障之類乍看不可解的錯誤回報。不是平假名或英文字母一律有問題,而是剛好第 2 位元組撞上符號值的那些字元才有問題 — 不懂結構就絕對推理不出來的現象。
CP932 — 自稱「Shift_JIS」的方言
另一個現場知識是機種相依字元。圓圈數字的 ①、㈱、羅馬數字的 Ⅱ、「髙」(「高」的異體字)— 這些原本不在 JIS 規格的字元集裡,是廠商各自追加的字元。Windows 把納入這些字元的擴充版 CP932 當作「Shift_JIS」來使用。
也就是說,世上被稱為「Shift_JIS 檔案」的東西,多數嚴格說來是 CP932。掛著同一個名字、內容卻微妙不同的表同時存在好幾張 — 想把含 ① 的資料用嚴格的 Shift_JIS 轉換就會失敗、亂掉的字元因環境而異,這些麻煩事的根源就在這裡。「牽涉到 Shift_JIS 的問題,先懷疑真正的表是不是 CP932」是實務上的訣竅。
即使如此,Shift_JIS 仍在服役的原因
Web 的世界幾乎已統一成 UTF-8。Shift_JIS(CP932)仍然沒有消失,是因為編碼的轉移不是「只要自己改掉就好」的問題。只要互相交換檔案的對象 — Excel 傳統格式的 CSV、運轉多年的核心系統、金融與行政系統的固定長度檔案介接 — 沒有一起邁步,交接的部分就會一直殘留舊的編碼。
因此在本課程中,我們不把 Shift_JIS 當作「過去的遺物」,而是「至今仍是推理對象的現役嫌疑人」。把 UTF-8 的檔案當成 Shift_JIS 讀、把 Shift_JIS 的檔案當成 UTF-8 讀 — 這兩個方向的誤讀模式要怎麼分辨,正是第 7 章綜合演習的主角。
小題 4-2 — 半形片假名、CP932 與仍在服役的原因
把 Shift_JIS 周邊的實務知識做個收尾。
Q4. 關於 Shift_JIS 中的半形片假名(アイウエオ…),正確的說明是哪一個?
半形片假名是從電報、早期終端機時代沿用下來的 1 位元組假名(源自 JIS X 0201),在 Shift_JIS 中收在 0xA1〜0xDF、只佔 1 個位元組。2 位元組字元的第 1 位元組(0x81〜0x9F、0xE0〜0xFC)被安排成避開這個區域,「Shift(位移)」這個名字正是來自這種挪移安排。第 7 章它會再次登場,解釋為什麼把 UTF-8 當成 Shift_JIS 誤讀時看起來會混入半形片假名。
Q5. CP932 與 Shift_JIS 的關係,正確的是哪一個?
Windows 的「Shift_JIS」實體是名為 CP932 的擴充版,追加了源自 NEC、IBM 的機種相依字元(①、㈱、髙 等)。即使同樣自稱「Shift_JIS」,不同環境的表卻微妙不同,於是含有 ① 的資料在別的環境亂掉、轉換失敗之類的事故就會發生。「聽到 Shift_JIS,實際上多半是 CP932」是實務上好用的經驗法則。
Q6. 在 UTF-8 已成為標準的現在,仍然會遇到 Shift_JIS(CP932)的原因,最貼切的是哪一個?
字元編碼的轉移不是「只要自己改掉就好」的事,而是所有互相交換檔案的對手系統一起邁步的問題。被雙擊用 Excel 打開的 CSV、金融與行政系統的固定長度檔案、老舊的 POS 與核心系統等,以 Shift_JIS 為前提的介接至今仍大量運轉。正因如此,「推理收到的檔案是哪種編碼」的技術(第 7 章)才具有實務價值。
本章帶走的重點
- Shift_JIS 是 ASCII 1 位元組 + 全形 2 位元組,但第 2 位元組侵入 ASCII 的值域(0x40〜0x7E)
- 地雷字元 = 第 2 位元組撞上 0x5C 等符號的字元(ソ 83 5C、能 94 5C、表 95 5C 等)。與跳脫處理相撞導致資料損壞
- 半形片假名是 0xA1〜0xDF 的 1 個位元組。Windows 的「Shift_JIS」實體是含機種相依字元的 CP932
- 因為 Excel 的 CSV 與老舊系統介接,它至今仍在服役。作為誤讀模式的推理對象,交手還會繼續
下一章是 Unicode 這一側的另一種編碼:UTF-16。這次的陷阱不是「位元組數」,而是「字元數的數法」。