前導 BOM 是一個位於解碼後文字最開頭、不可見的 U+FEFF 字元,而移除它的意思是從字串的第零個位置精確刪除其中一個字元,同時保留其他所有出現位置。BOM 移除工具正是做這件事:貼上你解碼後的文字、執行該工具,它就會在有出現的情況下刪除一個前導 U+FEFF,然後回報是否有任何字元被移除。無論你何時需要在儲存檔案、貼到解析器之前,或將文字餵給其他工具之前進行快速的本地清理,請隨時直接前往 /dev/bom-remover/ 開啟該工具。

許多開發者第一次遇到 U+FEFF 時都吃了苦頭:腳本拋出 "unexpected token"、CSV 匯入讓第一欄偏移一個欄位,或是 JSON 解析器拒絕讀取檔案,因為第一個字元不是它預期的大括號。在 UTF-8 中,那個不可見字元就是位元組順序標記 EF BB BF,雖然它是有效的 Unicode,但極少作為應用程式資料的第一個有效字元。最乾淨的修正方式是從字串開頭精確移除其中一個,然後確認文字中其他位置沒有藏著其他不可見內容。

how to remove bom
how to remove bom

前導 BOM 究竟是什麼

U+FEFF 是 "ZERO WIDTH NO-BREAK SPACE"(零寬度不中斷空格)字元,Unicode 標準也將其定義為串流開頭的位元組順序標記。當檔案以 UTF-8 連同 BOM 儲存時,編輯器會在第一個可見字元之前寫入三個位元組 EF BB BF,解碼器則會將那些位元組還原為結果 JavaScript 或 Python 字串中索引為零的單一 U+FEFF 碼點。該字元沒有寬度、沒有字形、也沒有聲音,但對解析器、正則表示式和長度檢查來說,它仍然算一個字元。根據 Unicode 關於 BOM 的 FAQ,該標記在 UTF-8 中是選擇性的,僅在建議無法以其他方式推斷編碼的檔案時使用,這也是為什麼 JSON 解析器、CSV 匯入工具和 shell 管線這類下游消費者經常會被它卡住的原因。

棘手的部分在於該標記是不可見的。人類讀者看到 "hello",解析器看到的是 "\uFEFFhello",這個差異就是成功與出現一個神秘語法錯誤之間的分別。正確的工具只會移除前導的那一個,如此一來,文字後段刻意出現的 U+FEFF,例如多語字串中的零寬度連接子,就會原封不動留在原位。

何時需要移除前導 BOM

你伸手去用 BOM 移除工具的最常見情境,都涉及一個會消耗解碼文字的管線。如果你從某個以 BOM 儲存的編輯器複製 JSON,然後丟進驗證工具,驗證工具會在讀到開頭大括號之前,先被第一個字元卡住。同樣的情況也會發生在:把 CSV 列貼進試算表、把字串餵給正則測試器,或是把承載內容交給權杖解碼器時。例如 JSON 驗證工具 在第一個字元是 U+FEFF 而非 "{" 或 "[" 時,會在第 1 欄回報剖析錯誤。

另一個常見情境是從一個預設為 UTF-8 連同 BOM 的 Windows 工具匯出報表或記錄檔。該檔案在瀏覽器中看起來沒問題,但使用 awk、cut 或 shell 迴圈的 Unix 腳本會把前導標記當作第一個欄位的一部分。在接收端、解碼之後將其移除,可以保持來源檔案原封不動,同時讓資料對那些假定第一個字元乾淨的下游工具來說是安全的。

逐步移除前導 BOM

  1. 確認你的文字已經解碼成字元;BOM 移除工具作用於字串層,而不是原始位元組。
  2. 在瀏覽器中開啟 BOM 移除工具,以便貼上區與狀態列同時可見。
  3. 將完整文字貼到輸入區,若有不可見的前導 U+FEFF,請一併包含進去,並保留所有內部或尾端的出現位置不動。
  4. 執行移除工具並觀察狀態列;它會告訴你剛好一個前導 U+FEFF 是否被移除,或是輸入內容並非以前導 U+FEFF 開頭。
  5. 在本地複製之前,請檢視完整輸出,檢查換行字元以及任何應該被保留、在後段刻意出現的 U+FEFF 內容。
  6. 將清理過的文字從輸出區複製到管線中的下一個工具,例如 JSON 格式化工具差異比對工具 進行驗證。

此工具應有的行為

此移除工具刻意設計得很狹隘:它只會觸及最前端的字元,而且只會在該字元正好是 U+FEFF 時才動作。如果你的文字以一般字元開頭,狀態會顯示未找到前導 BOM,而且輸出會與輸入逐位元組相同。如果你的文字以兩個連續的 U+FEFF 字元開頭,只會移除第一個,第二個會保留在原位,這在你確實有內容以編碼標記之後的零寬度不中斷空格開頭時很有用。

輸入開頭為輸出開頭為狀態意義
一個 U+FEFF,接著可見文字只有可見文字剛好移除一個前導 BOM
兩個 U+FEFF,接著可見文字一個 U+FEFF,接著可見文字移除一個前導 BOM,第二個保留下來
位置 0 沒有 U+FEFF輸入不變沒有出現前導 BOM
可見文字中包含 U+FEFF輸入不變依設計保留內部的出現

當你也基於合法目的而仰賴零寬度字元時,例如分隔表情符號序列或標記雙向文字,這種狹隘的行為就很重要。一個把所有 U+FEFF 都移掉的粗糙剝除器會損壞那些序列,而 BOM 移除工具會讓它們保持完整,只作用於第一個字元。

在繼續之前驗證結果

執行移除工具之後,稍微看一下輸出,確認沒有任何多餘的不可見字元殘留。一個快速的健全性檢查是將結果貼到 ASCII 表 參考資料中,檢查是否有任何十進位值為 65279 的字元(也就是 U+FEFF 的數值形式),或是將結果丟進 正則測試工具 中,使用樣式 /^\uFEFF/ 來確認開頭沒有匹配。你也可以用 差異比對工具 比較原始文字與清理後的文字,精確地看到檔案頭部是哪一個字元被影響,這能讓你對更下行的內容沒有被改動感到放心。

當狀態列回報成功移除時,請複製輸出並繼續執行原本的工作。如果狀態回報未找到前導 BOM,那麼你正在除錯的問題就完全是別的地方,最可能是另一層級上有零星空格、智慧引號或編碼不符,而 BOM 移除工具正好是用來排除最簡單解釋的正確工具。

移除 BOM 時的常見陷阱

貼上包含 EF BB BF 序列的原始位元組,而不是解碼後的 U+FEFF 字元,會讓移除工具無法偵測到前導標記,因為輸入區預期的是文字字串,而不是位元組串流。請先解碼再貼上。另一個陷阱是透過在匯出時會加回 BOM 的編輯器重新儲存清理後的文字,這會默默地還原你的清理結果。可能的話,請將編輯器的預設改為不含 BOM 的 UTF-8,或是透過明確省略編碼簽章的工具來儲存清理後的文字。

陷阱症狀修正方式
貼上原始位元組而非解碼後的文字狀態顯示找不到 BOM,但檔案其實有 BOM先解碼檔案,再貼上產生的字串
編輯器在儲存時重新加入 BOM清理後的文字仍在下游觸發相同的錯誤使用不含 BOM 的 UTF-8 重新匯出
移除所有 U+FEFF 而不只移除一個破壞表情符號或 RTL 序列中的零寬度連接子使用只鎖定前導出現的工具
對二進位檔執行輸出看起來損壞或偏移只餵給移除工具解碼後的文字,絕對不要餵原始二進位

這在開發者工作流程中的定位

一個實用的工作流程分三步:識別、移除、驗證。首先,將可疑字串餵給一個驗證工具或差異工具以識別問題字元,正如 如何檢查你的 JSON 格式是否正確 一文中所描述的那樣。其次,使用 BOM 移除工具來剛好移除一個前導 U+FEFF,而且不更動其他任何內容。第三,重新執行同一個驗證工具,或透過 差異比對工具 比較原始與清理後的輸出來驗證,該工具會在檔案頭部顯示出一個字元的差異。這個三步迴圈能在不擾動字串後段任何合法零寬度內容的情況下,捕捉到前導 BOM。

當你將移除工具和 JSON 格式化工具、JSON 驗證工具以及差異比對工具一起開在分頁中時,你就擁有了一個小型、以瀏覽器為基礎的清理文字匯出工作站。由於 BOM 移除工具是在貼上的文字上於本地端執行,你的字串從不離開瀏覽器,這在文字包含設定、憑證或任何你不想傳送的內容時很重要。

延伸閱讀:Chmod 計算機:八進位與符號表示轉換工具

延伸閱讀:在瀏覽器中將 Cookie 標頭轉換為 JSON 並轉回

延伸閱讀:如何在 CSS 中製作 3x3 格線:實務逐步指南