雜記 › 技術筆記 › 文本合併後行數對不上:缺尾末換行、被吃掉的 BOM 和不可靠的順序

文本合併後行數對不上:缺尾末換行、被吃掉的 BOM 和不可靠的順序

兩個 txt 檔案拖進來合併,每個各 2 行,結果只有 3 行。中間那一行是 bravocharlie:前一個檔案的最後一行與後一個檔案的第一行被焊在一起,沒有任何提示。行數對不上的排查,就是從這裡開始的。

合併在這個工具裡只有一次 join。整個前端 234 行、零外部依賴,分隔符下拉的預設檔「無分隔」在程式碼裡被對應成一個長度 0 的空字串,於是 alpha + bravo(11 位元組)拼上 charlie + delta(13 位元組)得到 24 字元 3 行。split 出來的三行是 alpha、bravocharlie、delta,被吞掉的那一行不會在任何面板上報錯。

根因是兩個檔案都沒有尾末換行:行尾缺的那一個位元組,正是下一個檔案的起點,拿空字串去補只會粘連。

勾選「插入檔案名標記」防不住粘連

看起來最靠得住的補救是把檔案名標記打開,但它加在內容前面。同樣這兩個檔案,預設檔下結果變成 76 字元 5 行,中間那行是 bravo===== tm_b_nonl.txt =====——粘連沒有消失,只是被標記字串蓋住了一段。把分隔符換到「空行」檔,結果才是 78 字元 7 行、逐行乾淨:空行替每個檔案之間墊出至少一個換行,標記和正文才各占一行。

檔位 結果 中間那行 判定 預設檔 3 行 24 字元 bravocharlie 粘連 +標記 5 行 76 字元 bravo===== tm_b_nonl.txt ===== 仍粘連 標記+空行 7 行 78 字元 (空行分隔) 乾淨 圖 1 兩個各 2 行檔案的三種檔位

每個檔案自己的 BOM 會被靜默移除

另一處隱形改寫發生在讀取階段。把檔案讀成文字用的那一句沒有帶編碼參數,瀏覽器按規範把每個檔案開頭的 U+FEFF 當作 BOM 剝掉,這是規範行為,不是 bug——但結果裡就再也沒有它了。拿兩個 7 位元組的帶 BOM 檔案去合,合併產物裡 U+FEFF 出現 0 次,總長 9 字元 9 位元組。同一對檔案、同一段合併程式碼,換一種讀法(不做 BOM 剝離)就會得到 U+FEFF 2 次、15 位元組。

工具不會替你保留原檔案的 BOM,下游按 BOM 辨識 UTF-8 的老腳本會把產物判成無簽名,而且不報錯。

textarea 吃掉 CR,統計卻不知道

把 CRLF 檔案和 LF 檔案放一起:crlf(8 位元組)加 lf(6 位元組)原樣拼接是 15 字元,含 2 處 \r\n。這段字串一進結果區的 textarea,瀏覽器就把它歸一成 LF——value 只剩 13 字元,0 處 \r\n。面板上那行「總字元數」是在歸一化之前算的,仍然顯示 15。於是下載下來的檔案比 stat 少 2 位元組。

面板統計 15 字元 2 處 CRLF 結果框 value 13 字元 0 處 CRLF 下載產物 比統計少 2 位元組 歸一化 圖 2 CR 被吃掉後三個數字不一致

複製或下載的都是這個已經被歸一的 value,兩者同源。new Blob([v]) 只塞了一個元素,前面沒有 U+FEFF,實測「複製 13 字元 / 下載 26 位元組」這組差額全部來自 UTF-8 的多位元組字元本身,BOM 造成的位元組差是 0。

順序既不是自然序,也不是選中序

在文本合併的模板裡搜排序相關的關鍵字是零命中——沒有 sort,也沒有 localeCompare,它壓根不排。一次多選 10.txt、2.txt、1.txt,結果順序保持 ten、two、one。

但這不等於順序等於你選中的順序。條目是各自讀取完成後依次推進清單的,誰先讀完誰先佔位。把 1,048,576 位元組的大檔案和 11 位元組的小檔案同批投放,小檔案排到了大檔案前面。四組同批投放的用例裡有一組跨了序。要固定的先後順序,就別指望投放這一步:先過一遍文本排序去重把行序理順,或者一次只放一個檔案。

「總行數」兩頭都不準

行數是把結果串按 \n 切開取段數,於是兩類誤差同時存在:檔案以換行結尾時,最後一段是空字串,恆多算 1 行——單個 CRLF 檔案顯示 3 行,真實正文只有 2 行;發生粘連時又少 1 行——上面那組 24 字元的結果顯示 3 行,實際應該是 4 行。所以它不能拿去和原檔案的行數對帳。

什麼檔案都會照單全收

沒有大小上限,也沒有格式白名單:檔案選擇器上寫的類型限制不會進入邏輯,拖什麼讀什麼。1 MiB 級別能跑完——big(1,048,576 位元組、131,072 個換行、帶尾末換行)加那個 11 位元組的 A,預設檔出 1,048,587 字元 131,074 行,粘連行是 bravoL000000;big 自己加自己換行檔,出 2,097,153 字元 262,146 行。跑完了,但統計三項裡沒有位元組數,體積要自己另外看。

二進位也一樣:一張 image/png 的 12 位元組 PNG 頭被當文字讀成 12 字元,裡面含 1 個 U+FFFD 和 1 個孤立的 \r;等 \r 被歸一成 \n,結果行數從 4 變成 5。GBK 的 9 位元組「中文測試」按 UTF-8 讀,變成 8 字元 21 位元組。這裡不做編碼辨識,只照讀。

清單頁的說法與程式碼不一致

把工具頁上那些可測說法逐條對著跑,17 條裡 12 條一致、5 條衝突。最硬的一條是「即時預覽合併結果」:改分隔符、勾標記、刪掉一個條目之後,結果區的 value 仍然是 24 字元,只有再點一次合併才更新成 37 字元——預覽不跟隨控件,跟隨的是那次點擊。另一條是分隔符只列了換行、空行、自訂三檔,實際下拉裡有第四檔「無分隔」,而且它才是預設檔,也就是把兩個檔案焊在一起的那一檔。

看什麼實測值能不能當真
中間那行24 字元 3 行當真,粘連直接看得見
總字元數15 對產物 13歸一化前算的,別當體積
總行數3 對實際 4尾末換行多 1、粘連少 1
BOM 數量U+FEFF 出現 0 次當真,讀的時候就被剝了
清單順序4 組用例 1 組跨序只當真到你能重現它

Q:合併後行數對不上,先懷疑什麼?

先懷疑尾末換行。兩個各 2 行的檔案出 3 行、中間一行是兩個檔案的內容拼起來的,就是缺尾末換行;把分隔符切到「空行」檔重跑一次,行數變 7 行、逐行乾淨。

Q:結果框裡的字元數和下載檔案不一致怎麼辦?

對一對有沒有 CRLF。混排那組統計顯示 15 字元、含 2 處 \r\n,產物是 13 字元、0 處 \r\n,差的 2 位元組就是被吃掉的兩行 CR。要在產物裡保住 CRLF,只能自己轉義之後另存。

Q:這幾個統計數字能拿去對帳嗎?

只能對同一批會話內部的相對差:三項統計裡沒有位元組數,行數兩頭不準,字元數算在歸一化之前,任何一項單獨看都會誤導。

合併這一步不做保護,也不通知改寫:尾末換行自己補、行序自己定、位元組自己核,粘連那行是唯一會主動露面的證據。

📎 相關文章

Excel 貼上轉 JSON:日期、長數字與精度在貼上那一刻就已經變了 2026-10-26 條碼解碼只吐原文:EAN-13 檢查碼、UPC-A 的前導零與 CODE128 的模 103 2026-10-26 二維碼解碼實測:糾錯等級救不了遮擋,每模組 2 像素才是生死線 2026-10-26 字頻還是詞頻:同一句中文,一份 23 項榜單和一份空表 2026-10-26 CSS 壓縮沒吃掉 calc 的空格,美化卻把 .btn:hover 劈成了兩截 2026-10-26