我想大家都有這種經驗,使用一個國外發展的軟體,進到台灣後經常會遇到許多中文亂碼的問題。想當然爾 Jenkins 也不例外,在許多使用情境下,遇到中文都會有亂碼的情況,本篇文章我要來分享關於這個「中文亂碼」的背景知識與解決方案。
背景知識
遙想數十年前,在電腦的世界裡中文編碼尚未有個定案、Unicode 也尚未出現的年代,大家都各自定義的中文編碼格式,光是一個「繁體中文」就有數總不同單位訂定的編碼標準,例如 Big5 (大五碼)、x-Chinese-CNS (中華民國國家標準)、x-Chinese-Eten (倚天中文系統) 等等。但無論各家的標準好壞,總之,一直以來社群裡最常用的 Big5 (大五碼) 編碼勝出,管他甚麼國家標準還是一線廠商,就是用最多的編碼勝出,所以我們現在台灣買到的 Windows 作業系統,都是採用 Big5 作為中文字的預設編碼。
我們都知道,使用 Big5 編碼問題非常多,原因就在於他使用兩個字元代表一個中文字。首先,他能在這兩個字元的所有字碼組合中能表達的字數有限,但其實也足以顯示出大部分常用的中文字。第二,像是所謂的 CJK 字元 (中文、日文、韓文) 都各自使用了重複的字碼區間來表達各國不同的文字,也就是若你用計算機的角度去看這些文字,相同的字碼組合 ( 例如 0x94A2 ) 在不同的字碼系統中,代表的就是不同的文字,這樣對於要在同一個網頁中出現多國語言的文字變得極其困難,也因為有越來越多這樣的需求,所以在 1991 年 10 月推出了 Unicode 1.0.0 版本,試圖統一全球所有語言的文字,用一套統一的標準來編碼全球文字,最初包含的文字有:阿拉伯字母、亞美尼亞字母、孟加拉文、注音符號、西里爾字母、天城文、喬治亞字母、希臘字母、古吉拉特文、古木基文、諺文、希伯來字母、平假名、卡納達文、片假名、寮文字、拉丁字母、馬拉雅拉姆文、奧里亞文、泰米爾文、泰盧固文、泰文字與藏文等等。
這個 Unicode 計畫立刻被全球注目,且陸續被各國軟體大廠接受,因此在近10年的作業系統,無論是 Linux, Windows, Mac OS 幾乎都內建 Unicode 的支援!
在 Windows 作業系統內,事實上所有在作業系統層級對於文字處裡的操作,都一律使用 UTF-16 編碼,這個統一文字編碼的動作,大幅的減少編碼絮亂的問題發生。不過只要牽扯到 輸入輸出裝置 ( I/O Devices ) 問題就會開始出現,因為在作業系統上執行的程式,並不是獨立存在的,很多時候需要在不同程式間交換資料,這就牽涉到輸入輸出,雖然 Windows 作業系統有內建自動偵測文字編碼的能力,不過編碼問題何其多,自動判斷不見得會準確,而且誤判的情況是很常見的,這部分說真的不是三言兩語可以說清楚的,不瞭解的人,你只要知道判斷文字檔的編碼是有點難度的即可。
因此,在 Windows 作業系統有區分支援 Unicode 程式與非支援 Unicode 程式兩種。如果應用程式預設採用 Unicode 處裡所有文字,那麼所有文字處裡都會以 Unicode 為主,在與作業系統之間進行輸入輸出 ( I/O ) 操作時,比較不會有問題。但另一類非 Unicode 程式就很難說了,因為作業系統必須「猜」你目前用的是甚麼文字編碼,當它猜不出來時,還是必須預設選用一種編碼來解析你的文字,而這個就是所謂的非 Unicode 程式的預設編碼,這個設定在「控制台」的「地區」設定中可以調整,如下圖示「非 Unicode 程式目前使用的語言」 為「中文 (繁體,台灣)」就是採用 Big5 編碼 ( CP950 ):
你可能會想問,現在都幾年了,大家不是都應該已經支援 Unicode 了嗎?還會有這麼多「亂碼」的問題呢?!原因無他,一切都是「歷史包袱」在作祟!
我們就以台灣為例,目前已經有太多的文字檔案預設使用 Big5 編碼,如果預設使用 Unicode 編碼,可能會導致更多「亂碼」的情況,所以有許多程式還是不敢僅支援 Unicode 文字編碼,所以當遇到有特定文字讀寫需求時,問題就來了。
話雖如此,Mac OS 就相對來的斬釘截鐵一些,在 Mac 作業系統中,早在前幾個版本就已將整個作業系統的編碼改用 Unicode 進行編碼,只要判斷不出文字編碼,一律就當 Unicode 來處裡,所以許多人使用 Mac 電腦經常會遇到中文檔案亂碼的問題,但這也要看你的文字從哪裡取得,只要文字檔案的編碼採用 Unicode 存檔,基本上不會有問題的,而且現在越來越多應用程式都預設儲存為 Unicode 編碼,所以這樣的問題一定會越來越少。不過你知道的,就算遇到亂碼問題,果粉一定會覺得這是自己的錯,然後自己想辦法把問題修復,但 Windows 用戶就會把微軟罵翻天的,哈哈!
所以,要等 Windows 繁體中文版的作業系統將預設編碼改成 Unicode 應該還要再等等,而預設對 non-Unicode 編碼的設定沒有改正,跟 Mac 作業系統之間的文字相容性永遠不會有解決的一天,這個結就看甚麼時候解開了! XD
回歸正傳,這件事到底跟 Jenkins 有甚麼關係呢?接下來要講重點了!
問題描述
使用 Jenkins 有幾個很重要的觀念:
- Jenkins 主要使用 Java 開發的,而 Java 雖然預設採用 Unicode 編碼,但 Java 為了 Windows 相容性考量,跟 I/O 相關的編碼還是以系統預設的 non-Unicode 編碼為主,也就是 Big5 編碼。
- Jenkins 使用許多 Plugins (外掛) 或 Build steps (建置步驟) 其實都是透過 命令提示字元 (Command Prompt) ( cmd.exe ) 來執行指令,而 Windows 命令提示字元,也是以 non-Unicode 編碼為主,也就是 Big5 編碼。
因為 Java 與 Windows 皆採用 non-Unicode 編碼為主,也就是 Big5 編碼,照理說,在大部分的情況下,只要「不使用」Unicode 就不會有問題,對吧?但這不可能啊!因為有許多程式早就以 Unicode 為主了,我舉一個最簡單的例子:Git
我們使用 Git 工具建立的每個版本都需要填寫紀錄 ( Log ),而所有的 Git Log 都預設採用 Unicode 編碼,如果你用 Big5 編碼來儲存 Log 訊息,當你 Push 到 GitHub 或其他遠端儲存庫平台後,看到的就只會是亂碼,所以我可以說:Git 只支援 Unicode 編碼的訊息文字。
以下我就用一個簡單的測試,帶大家重現這個問題發生的過程:
- 這是一個 Jenkins 預設安裝,且只安裝過 GIT plugin 而已。我們直接新增作業:
- 設定一個作業名稱,並選擇「建置 Free-Style 軟體專案」
- 設定 Git 原始碼管理,讓建置作業開始時,自動將原始碼取回
- 設定一個 建置步驟 (Build steps),並設定 echo 測試中文 測試看看是否會有中文亂碼問題,然後儲存設定。
- 執行「馬上建置」來測試看看
- 此時你可以發現,從終端機輸出來看,完全沒有亂碼產生,一切看似非常完美!
上面這張圖片,我想特別解說一下,這個看似完美的輸出,其中藏有一些不為人知的細節,我將上圖編號一一說明如下:
- Jenkins 的網頁輸出預設已經採用 UTF-8 編碼,所以網頁上看到的中文字,都是用 UTF-8 輸出的。
- 這也是由 Jenkins 執行建置作業時的輸出內容,基本上這類由 Jenkins 輸出的文字,都不會有編碼轉換的問題。
- 這部分文字是執行 Windows 批次命令時輸出的指令,預設當然是 Big5 編碼,不過當 Jenkins 讀取 Console 的標準輸出 (stdout) 時已經知道這些 Console 標準輸出為 Big5 編碼,所以 Jenkins 會自動以 Big5 編碼讀入文字,並先將讀入的文字轉成 Unicode 編碼,等輸出到網頁的時候再轉換為 UTF-8 編碼。
- 這段執行 echo 而輸出的文字,一樣會以 Console 預設的 Big5 編碼輸出,Jenkins 一樣會自動以 Big5 編碼讀入文字,並將讀入的文字轉成 Unicode 編碼,等輸出到網頁的時候再轉換為 UTF-8 編碼。
接下來,我們針對我們設定的遠端儲存庫進行一次 git commit / git push 動作,輸入的 commit log 必須為中文:
接著我們在執行一次「馬上建置」,這次再看終端機輸出還是正常的
不過如果你點擊本次建置的「狀態」就會發現開始有亂碼產生了,如下圖示:
這個亂碼是怎麼來的呢?
因為 Git 工具是一套以 Unicode ( UTF-8 ) 為預設編碼的「支援 Unicode 程式」,所以任何輸入與輸出,預設皆以 Unicode 為編碼處理,所以當你透過 Git 工具或 Git API 讀取 Git Repository 裡面的 Log 訊息時,他的輸入與輸出都還是以 Unicode 為預設編碼,但我們的 Jenkins 基本上預設使用 Java 的預設字集來處理不同程式間的 I/O 操作,所以 Java 會以 Big5 字集去解讀 Unicode 字集的字元,所以結果當然就是亂七八糟啦!
接著,我們再換個方式測試,我們直接在先前設定的建置步驟修改一下指令,多加上一行 git log -1 顯示最近一 次的 Git 變更紀錄 (會有中文字)
然後再執行一次「馬上建置」,這次你就會從 終端機輸出 (Console Output) 看見這行指令所輸出的亂碼文字了:
接著我們再改一次建置步驟所下的指令,將第二行指令修改為 git log –1 --encoding=big5 以 Big5 編碼顯示最近一 次的 Git 變更紀錄:
在執行一次建置,你就會看到正確的編碼顯示了:
※ 關於 git log 的相關指令說明,可以直接從命令提示字元下輸入 git help log 取得相關文件。
解決方案
這篇文章寫了這麼長,你能看到這裡算你厲害! XD 希望你透過上述背景知識與問題描述可以得知為何在一個明明支援 Unicode 的環境下會有「中文亂碼」的情況發生。
對於這個問題的解決方案,我相信你已經大概知道個方向,那就是:
- 在終端機模式下 ( Console Mode ) 所使用的字集,必須與你所執行的指令或程式所用的字集一致,否則就會出現亂碼的情況。
不過,很多時候我們無法控制第三方程式 ( 3rd party program ) 到底預設使用甚麼編碼,而且像是我們文章稍早所顯示的【建構 #2 狀態】頁面所顯示的亂碼,這段文字是由 GIT plugin 輸出的,我們無法更動他背後執行 Git 命令的參數,所以這頁亂碼的問題很難解決,除非我們去修改 GIT plugin 的 Java 原始碼並重新編譯,讓他下載 git log 指令時可以加上 --encoding 參數,但修改別人家的原始碼太麻煩了,而且也不是所有人都會修改 Java 原始碼,因此我通常不建議使用這種解決方案。
我認為,要讓這個世界進步,除了 IE 必須死外,還有一點就是最好全世界都用 Unicode,但你知道的,這兩個願望可能在未來 10 年都很難達成,所以我們才需要想出一個好法子來解決目前的亂碼困境。
我的解決方案是這樣的:
- 將 Java 的預設字集修改為 UTF-8 編碼,也就是讓 Java 在執行的時候,所有預設編碼都以 UTF-8 為主。
- 同時也要修改 Console 預設的輸入輸出字集以 UTF-8 為主。
這兩個條件只要都能達成,讓我們的 Console 與我們的程式都一律採用 UTF-8 做編碼,基本上問題就會迎刃而解。不過也許有人擔心,如果我們執行的外部程式「只」支援 Big5 編碼怎麼辦?其實這個問題是有可能發生的,但機率非常低,基本上你只要不是執行 10 年前寫出來的命令列程式,那麼通常這些工具程式大多都支援 UTF-8 才對,而且各位還記不記得本文稍早講過,在 Windows 底層全部都是以 Unicode 編碼,所以這些程式在 Console 模式下執行並輸出文字時,其輸出的編碼會透過 Windows 自動轉換為目前所設定的編碼,因此發生亂碼的機率非常低。
要做出這個調整,總共有兩個步驟:
1. 將 Java 的預設字集修改為 UTF-8 編碼
你只要設定一個 JAVA_TOOL_OPTIONS 系統環境變數,並將其值設定為 -Dfile.encoding=UTF8 即可。或你也可以直接以系統管理員身分開啟命令提示字元視窗,然後執行以下 SETX 命令,這樣也可以自動將環境變數新增到系統裡。
SETX /M JAVA_TOOL_OPTIONS -Dfile.encoding=UTF8
※ 請注意:設定完環境變數後,必須重新啟動 Jenkins 才會生效,如果你是用命令提示字元啟動 Jenkins 的,必須關閉現有的命令提示字元,並重新啟動一次 Jenkins 才會生效。
如此一來,我們的 Jenkins 就會完全以 UTF-8 處裡所有程式的輸入輸出。不過千萬不要只做這個步驟,你要知道 Windows 的 終端機模式 (Console Mode) 預設還是以系統預設字集 (Big5) 作為預設編碼,因此當你設定完這個選項,並重新啟動 Jenkins 之後,你遇到的亂碼問題會更多,因為執行環境採用的編碼與終端機模式採用的編碼不一致,會導致原本沒亂碼的,現在反而會一堆亂碼喔!
如下圖示,當我設定好環境變數,並重新執行一次「馬上建置」後,你在終端機輸出將會看到一堆亂碼 (部分可以看到中文、部分看不到中文)。
所以,你需要修改第二個步驟:
2. 修改 Console 預設的輸入輸出字集以 UTF-8 為主
在 Windows 終端機環境下,有個 CHCP 命令 ( Change the active console Code Page ),基本上終端機預設編碼是跟著「控制台」的「地區」設定裡的「非 Unicode 程式目前使用的語言」 走的,當你設定為「中文 (繁體,台灣)」的時候,就是採用 Big5 編碼 ( CP950 )。基本上這裡的 Code Page 就代表著某個編碼的字集,且以「數字」作為編號,你可以到維基百科的 Code page - Wikipedia, the free encyclopedia 查詢全世界有哪些字集可用,以及那些字集代表的是哪一種文字編碼。這裡我們最常用的就是 950 代表大五碼 (Big5),而 65001 就是常見的 UTF-8 Unicode 編碼。
因此,請開啟你目前所有的 Jenkins 專案組態設定,將自訂的「執行 Windows 批次指令」的第一行都加上以下命令:chcp 65001
再執行一次「馬上建置」並再次查看終端機輸出,此時你會看到完全正常的中文輸出
然後你從 Git Repository 在 commit / push 一版上去 (使用中文訊息),在執行「馬上建置」一次,並且切換到建構的「狀態」頁面,此時你看到的就會是完全正常的中文顯示了!
只要你能做到這兩點,Jenkins 的亂碼問題就算是完整解決囉! ^___^
心得分享
我個人在編碼方面的研究算是有很多年的歷史,從 15 年前開發出第一套「繁簡轉換」產品,到後來每次遇到編碼問題的研究,累積了很多不為人知的 Know-How,說實在的沒啥用處,因為看到一點點亂碼好像無傷大雅,大不了都改成 Unicode 就可以了,只是很多時候不是你說想改成 Unicode 就可以改成 Unicode 的,所以學會這些技術的細節,唯一的好處就是你比較有方向知道如何設計解決方案。
像這篇文章寫這麼長,也一定有人覺得我莫名其妙,不就執行一行 setx 指令設定系統環境變數,外加多執行個 chcp 65001 就可以解決嗎?何必廢話這麼多!其實我自己也覺得蠻莫名其妙的,我就很單純想把我所知道分享出來而已,寫下來,或許有人看得懂,至少這點經驗可以傳承給某個有緣人,而且自己也可以從寫作過程中獲得並釐清一些自己也覺得似是而非的觀念,其實也是個雙贏的結果阿! ^__^
相關連結