Bash 可以透過 sed 或 awk 的一行指令為每一行加上引號,但結果只對單一特定情境有效:一個沒有內嵌引號、逗號或反斜線的純文字清單。當某一行包含目標格式會特別處理的字元時——JSON 中的雙引號、SQL 中的單引號、CSV 中的逗號——一行指令就會悄悄產生損壞的輸出,造成編譯、執行或匯入失敗。JSON 依據 RFC 8259 需要將內嵌的雙引號以反斜線跳脫,SQL 依據 PostgreSQL 與 SQLite 使用的 ISO 規則將單引號加倍,CSV 則依據 RFC 4180 將內嵌的雙引號加倍。Bash 對這些規則完全沒有內建認知。理解「為一行加上引號」與「正確跳脫」之間的差異,就是把一個能跑的示範轉變為可直接貼上之輸出的關鍵,而這正是 Add Quotes to Each Line 工具針對每種預設所處理的事。

常見的 Bash 一行指令:將每一行包入引號
有四種 shell 慣用法可涵蓋大部分 Bash 中「為每一行加上引號」的工作:sed、awk、while-read 圈,以及 xargs。每種都接受一個每行一個項目的清單,並輸出相同的清單,但每一行都被一對固定的引號字元包起來。如需深入探討以 awk 為主的這條路徑,請參考 awk 為每一行加上引號指南。
最短的 sed 形式使用 & 元字元,它會展開為整個符合的行:
sed 's/.*/"&"/' list.txt > quoted.txt
Awk 則透過一個 print 陳述式達到相同的輸出,將字面上的引號串接在 $0(也就是目前的整行)前後:
awk '{ print "\"" $0 "\"" }' list.txt > quoted.txt
while-read 迴圈雖然較為冗長,但不會為每一行衍生(fork)出子 shell,這在輸入有數十萬筆資料時特別重要:
while IFS= read -r line; do printf '"%s"\n' "$line"; done < list.txt > quoted.txt
其中 IFS= 與 -r 旗標相當重要:少了它們,開頭與尾端的空白會被去掉,反斜線也會被解讀為跳脫序列。xargs 也能透過 -I{} 預留位置,一次餵入一行輸入來完成這個工作:
xargs -I{} echo '"{}"' < list.txt > quoted.txt
當輸入是乾淨的 ID、名稱或檔名清單,且不含內嵌引號、逗號或反斜線時,這四種方式都會產生看起來正確的輸出。問題只會在輸入不再乾淨時出現。
Bash 一行指令悄悄出錯之處
sed 與 awk 形式只是把每一行包進字面上的引號字元,完全不會更動該行的內容。這聽起來無害,直到內容中出現目標格式會特別處理的字元。
以一個包含 say "hi" 這行的清單為例。Sed 會把它包成 "say "hi"",這正好是會讓 JSON 解析器壞掉的輸出。Bash 的一行指令完全不會察覺任何錯誤,因為 shell 正愉快地把位元組傳遞過去。最終壞掉的是讀取檔案的那一方——通常是在離真正出錯的那行很遠的地方,冒出語法錯誤。
同樣的問題也會發生在 SQL:當一個值包含單引號,例如 it's,需要被包成 'it''s'。一個用 sed 將每行包進單引號的一行指令會輸出 'it's',這會在 it 之後關閉字串字面值,導致該行剩下的部分變成壞掉的 SQL。CSV 也有自己的變體:一個像 3" monitor 的值必須變成 "3"" monitor",其中的引號要加倍,否則大多數匯入工具會在內部引號處截斷欄位,或造成欄位錯位。
其他安靜的失敗還包括:空行(會被包成 "",這不一定是你想要的)、在引號欄位內部變得有意義的開頭與尾端空白、Windows 路徑與正則字串常見的來源中出現的反斜線,以及某些下游工具讀取時會解讀成不同位元組序列的 Unicode 字元。這些問題在 Bash 輸出中都看不見。它們會在稍後——在你已經繼續往下做之後——才在解析器、查詢或試算表中浮現。
如何在 Bash 中正確地為每一行加上引號
- 決定輸出要送進哪種格式:JSON 陣列、SQL IN (...) 子句、CSV 資料列,或其他。每種情況的跳脫規則都不同,選錯就是全部失敗的原因。
- 如果清單是髒的——有重複、空行、混雜空白——先用 Bash 清理它。幾個一行指令就能處理大部分情況: sort -u list.txt # 去除重複 awk 'NF' list.txt # 移除空白行或只有空白字元的行 sed 's/^[[:space:]]*//; s/[[:space:]]*$//' list.txt # 修剪每行 把清理過的清單管線傳給下一步。
- 在瀏覽器中開啟 Add Quotes to Each Line。這個工具在你的機器上本地執行——不需要上傳、不需要帳號、頁面載入後也不需要任何網路往返。
- 把清理過的清單貼到輸入區,每行一個項目。
- 點選符合你所需格式的預設。JSON 陣列會將每一行包入雙引號、以逗號連接,並在結果外加上方括號。SQL IN 會將每一行包入單引號、以逗號連接,並在清單外加上括號。CSV 資料列會將每一行包入雙引號、把任何內嵌的雙引號加倍,並將整列輸出為單行,以便貼進試算表的匯入功能。Plain quotes 則只以選擇的引號字元包住每一行,不加上任何外括符號。
- 如果預設的預設值不夠精確,可以調整各項控制:變更引號字元、切換分隔符號、開啟或關閉跳脫、略過空行、修剪每行,或為需要的格式加上結尾分隔符號。「最後一行不加逗號」是預設行為,也是 JSON 與 SQL 的正確選擇。
- 用複製按鈕複製結果。工具會回報它處理了多少行,你可以拿來與輸入比對,作為健全性檢查。
如果你執行過一次工具後需要不同的設定,請從原始清單重新開始,而不是從先前的輸出。對工具自己的輸出再執行一次,會把它再包一層引號,因為工具無法判斷既有的引號是內容還是先前包進去的。
JSON、SQL 與 CSV:三種不同的跳脫規則
同一份輸入在每種預設下會產生不同但都正確的輸出。下表整理了每種格式實際要求的內容,這些內容取自它們所引用的標準。
| 格式 | 引號字元 | 內嵌引號規則 | 輸出外框 |
|---|---|---|---|
| JSON 陣列 | 雙引號 " | 以反斜線跳:" 變為 \",依 RFC 8259 | [ ... ] |
| SQL IN 清單 | 單引號 ' | 引號加倍:' 變為 '',依 PostgreSQL 詞彙規則與 ISO 標準 | ( ... ) |
| CSV 資料列 | 雙引號 " | 引號加倍:" 變為 "",依 RFC 4180 | 單行,以逗號連接 |
| 反引號(JavaScript 樣板) | 反引號 ` | 以反斜線跳脫:` 變為 \`,並將插入運算式的開頭 ${ 跳脫以避免運算式注入 | 以選擇的分隔符號連接 |
一個包含 say "hi" 的行,在 JSON 中會變成 "say \"hi\"",在 SQL 中則是 'say "hi"'(在單引號包住的 SQL 字串中,雙引號並非特殊字元,但內嵌的單引號要加倍),在 CSV 中則是 "say ""hi"""。這三種規則彼此不同,而它們的差異正是每種預設所編碼的內容。
什麼時候 Bash 不適合這項工作
當輸入是乾淨的、目標也是 Bash、shell 管線或純文字檔時,Bash 就是合適的工具。要把一串檔案路徑傳給 rm 或 ls 並不需要依格式跳脫,這時一個 sed 或 awk 的一行指令正好是恰到好處的努力程度。
當清單要送進有自己的跳脫規則的程式碼或資料時,Bash 就不合適了。shell 無法知道 JSON 想要以反斜線跳的引號、無法知道 SQL 想要加倍的單引號,也無法知道 CSV 想要加倍的雙引號。試圖用一個巧妙的 sed 運算式同時處理這三種格式,通常會產生一個連團隊裡的人在六個月後都讀不懂的一行指令——包括當初寫它的人。
在這些情況下,Add Quotes to Each Line 工具正是圍繞這個區別而設計的。這些預設與其所引用的標準緊密對齊,因此跳脫行為是文件明載的結果,而不是手動拼湊出來的。輸入上限為一百萬字元,並以單一線性方式處理,所以即使是很長的清單也能瞬間回傳。一切都在你的瀏覽器中執行——對於那種你不太想隨便貼到某個網站的 ID 或名稱清單來說,這才是正確的預設。
Add Quotes to Each Line 中實用的控制項
每個預設都會顯示同一組底層控制,讓你不需要離開頁面就能調整結果。
- 引號樣式:雙引號、單引號、反引號,或無(適用於你只想變更分隔符號的情況)。
- 分隔符號:預設為逗號,但對於使用其他分隔符號的格式,也可以使用空格、分號或直立線符號。
- 結尾分隔符號:預設為關閉,因此 JSON 陣列不會以多餘的逗號結尾。可以切換為開啟,為需要的格式加上它。
- 跳脫:在 JSON、SQL 與 CSV 預設中預設為開啟。關閉後會產生不更動字元的原始包覆輸出。
- 略過空行:移除空行或只有空白字元的行,避免它們在輸出中變成多餘的空項目。
- 修剪每行:移除每行開頭與尾端的空白,當來源清單是從表格或聊天訊息複製貼上時特別有用。
工具會回報它處理的行數,這是最簡便的方式來確認沒有任何輸入被悄悄漏掉。如果來源清單含有重複、空行或雜亂空白,請先清理它,再把清理後的輸出餵給 Add Quotes to Each Line。每一層都應該把一件事做好。
延伸閱讀:從逗號到欄位分隔符號:建立引號安全的輸出。