Windows 的記事本以及多個 Windows 編輯器在儲存 UTF-8 檔案時,會在檔案最前端加上一組三個位元組的位元組順序標記 (EF BB BF),而這個單一的簽章就是在此平台上「UTF-8 with BOM」(帶 BOM 的 UTF-8) 的含義。任何程式一旦讀取該檔案並將位元組解碼為 Unicode 字元後,這三個位元組就會塌縮成位於位置零的一個不可見字元:U+FEFF。從這一刻起,BOM 已不再是位元組層級的問題;它是一個位於 JavaScript 字串開頭的單一不可見碼位,而這正是 BOM Remover 工具運作的層級。整個轉換過程完全在瀏覽器分頁內進行,不會上傳任何資料,狀態面板會在你把內容複製回檔案之前,告訴你位於開頭的 U+FEFF 是否確實被找到並移除。內部內容、行尾符號,以及後續出現的任何 U+FEFF 會刻意保留,因為 Unicode 將非起始位置的 U+FEFF 視為內容的一部分,而非編碼簽章的一部分。

how to remove bom from utf-8 file in windows
how to remove bom from utf-8 file in windows

為何 Windows 儲存的 UTF-8 檔案會帶有 BOM

Windows 的記事本預設使用「UTF-8 with BOM」來儲存純文字檔,這會在檔案最前端加上三個位元組的序列 EF BB BF。這個序列就是 UTF-8 位元組順序標記,也是每個 Windows 工具在標示檔案為「UTF-8 with BOM」時所指的意思。任何程式一旦讀取該檔案並將位元組解碼為 Unicode 字元,這三個位元組就會塌縮成位於位置零的一個不可見字元:U+FEFF。從記憶體中字串的角度來看,已經不再存在 EF BB BF;BOM 已經變成一個位於文字開頭的單一碼位。

BOM 對 UTF-8 來說並非絕對必要。由於 UTF-8 擁有固定的位元組順序,Unicode 協會明確建議不要在 UTF-8 編碼的文字上使用 BOM,而 WHATWG 編碼標準也將開頭出現 EF BB BF 視為多種可能的簽章之一,而非必要條件。Windows 仍然使用它,是因為較舊的 Microsoft 工具過去依賴某種簽章來識別編碼,這個習慣也延續到了現代的記事本中。這個簽章在大多數編輯器中是不可見的,這也是為什麼這麼多開發者直到腳本、剖析器或 shebang 行無法運作時,才會發現它的存在。

開頭的 BOM 何時真的會造成問題

大多數 Windows 編輯器都能把開頭的 BOM 隱藏得夠好,讓你忘了它的存在。一旦有個不預期它存在的工具讀取該檔案,這個不可見字元就會變成一個真實的首字元錯誤。接收的程式會看到一個以 U+FEFF 開頭、而非檔案作者預期字元的字串,而這個單一的不一致就足以讓剖析作業出錯。

Windows 上的情境開頭的 U+FEFF 實際造成的影響
Unix 風格的腳本或 shebang 行直譯器看到的是「\uFEFF#!」而非「#!」,可能會拒絕該檔案,或將 BOM 視為指令名稱的一部分。
CSV 的第一欄標頭標頭鍵值會被加上 U+FEFF 前綴,因此查找「name」時會靜默失敗,合併操作也無法對應。
嚴格的 JSON 剖析器許多剖析器會拒絕位於位置零的 U+FEFF,並在表面上看起來空白的檔案中,於第 1 行第 1 欄產生剖析錯誤。
差異比對或合併工具兩個看起來完全相同的檔案,可能會回報每一行都不同,因為其中只有一個帶有這個不可見的首字元。

如果你的檔案正引發上述任何症狀,而且已經排除真正的內容問題,那麼開頭的 BOM 就是極有可能的肇因,在重寫可運作的程式碼之前值得先加以調查。

在 Windows 中從 UTF-8 檔案移除 BOM

能安全完成這項工作的工具就是 BOM Remover。它運作於已解碼的字串層級而非原始位元組,這對於 Windows 檔案在解碼器將其還原為字元後的處理,才是正確的層級。下列步驟無論你使用哪個 Windows 編輯器建立檔案都能適用,因為在初次複製之後的每一步,都是對已解碼文字進行操作。

  1. 在能讓你複製原始內容的編輯器中開啟檔案(記事本、Notepad++、VS Code,或任何能顯示文字的工具)。避免透過可能會移除或加入不可見字元的所見即所得程式來貼上。
  2. 選取檔案的全部內容,並使用平常的 Ctrl+A、Ctrl+C 快捷鍵將其複製到剪貼簿。
  3. 將內容貼到 BOM Remover 的輸入區域。該工具單次最多接受 200,000 個 UTF-16 碼單位的輸入,足以涵蓋一般大小的原始檔、組態檔以及小型資料匯出。
  4. 執行移除工具並讀取狀態列。它會說明是否移除了位於開頭的一個 U+FEFF,或是根本沒有找到開頭的 U+FEFF,並以 UTF-16 碼單位回報新的輸出長度,讓你能確認變更確實生效。
  5. 檢視完整的輸出,包括行尾符號以及任何刻意存在於後段的 U+FEFF 內容。內部、結尾,以及第二個開頭的 U+FEFF 字元都是刻意保留的。
  6. 將結果複製到本機,並使用編輯器中「UTF-8 without BOM」或「UTF-8」編碼選項,將其存回新檔案(或覆寫原檔),以免檔案再次以相同的簽章重新產生。

如果狀態回報找不到位於開頭的 U+FEFF,那麼問題就與 UTF-8 BOM 無關,而該工具也會刻意保留你的文字不動。如需一份聚焦於檔案層級處理,而非 Windows 特有成因的相關逐步說明,請參閱於瀏覽器中本地移除檔案 BOM 的逐步指南

BOM Remover 會變更與不會動到的內容

整個工具圍繞著單一規則打造:位置就是一切。它會將 input.charCodeAt(0) 與十六進位值 FEFF 進行比對,若首字元相符,則回傳 input.slice(1);否則原封不動地回傳原始輸入字串。沒有 trim、沒有全域取代、不會掃描其他不可見字元,也不會重寫行尾符號。這樣狹隘的契約正是這個工具能安全套用於可能於他處含有合法 U+FEFF 文字的原因。

輸入中 U+FEFF 的位置BOM Remover 的處理方式
第一個字元(開頭的 BOM)移除;輸出為字串的其餘部分,摘要會標示為成功移除。
在另一個 BOM 之後的第二個字元第一個 BOM 會被移除;第二個會成為新的首字元並保留原位。
字串中間、行尾換行之後,或字串結尾完整保留。Unicode 將這些視為零寬不中斷空格內容,而非編碼簽章。
輸入中唯一的字元移除;結果面板會呈現空白輸出,摘要仍會標示為成功移除。
位置零不存在 U+FEFF輸出與輸入完全相同;摘要會回報找不到開頭的 BOM。

CRLF 與 LF 行尾符號、Tab 鍵、NUL 位元組、表情符號、組合標記、代理對,以及非拉丁文字,皆會原封不動地通過。JavaScript 的詞法文法將位於非起始位置的 U+FEFF 視為帶有空白性質的內容,這也是該工具會保留它們的原因;若需了解該語言本身如何分類這些字元的詳細資訊,請參閱MDN 關於 JavaScript 詞法文法的參考資料

在 BOM 進入檔案之前就擋下它

事後清理檔案只是應急手段,並非真正的解決方法。若你能控制檔案在 Windows 上的儲存方式,請選擇一開始就不加上 EF BB BF 的儲存器。Windows 10 及之後版本的記事本提供了一個不含 BOM 的純粹「UTF-8」選項,而大多數第三方編輯器(例如 Notepad++、VS Code 和 Sublime Text)則在其編碼選單中,將「UTF-8 without BOM」預設為預設值,或提供一鍵切換的功能。選定該選項一次,之後儲存的檔案就不會再帶有這個不可見的首字元,也讓你不必每次都伸手去用移除工具。

如果你是從無法更改的來源(廠商、同事使用的較舊工具、舊式匯出腳本)收到檔案,BOM Remover 仍然是處理解碼階段問題的合適工具。它無法讀取原始位元組,因此無法告訴你原始檔案究竟是帶有 EF BB BF 簽章、UTF-16 的 FE FF 簽章,還是任何其他位元組層級的標記;這項差異只有在解碼器執行之前才有意義。一旦某個 Windows 編輯器開啟了該檔案,並把開頭的 U+FEFF 暴露到你的剪貼簿,這個工具的適用範圍就恰到好處:位置零,僅此而已。當開頭的 BOM 干擾到剖析器、比對、shebang、欄位名稱或其他首字元時,請使用它;而當你能自行選擇編碼器時,則應採取編輯器層級的修正。

延伸閱讀:如何在 Windows 上檢視剪貼簿文字以進行除錯