HOWTO · Bash
Bash 中的字串替換
本教程演示了在 bash 中將一個子字串替換為另一個字串。
本頁內容
請依文字所在位置選擇替換方式:Bash 參數展開會根據目前 shell 中的變數計算替換後的值,而不會修改該變數;sed 會轉換資料流中的各行;awk 則適合在逐筆記錄處理時進行替換。每個範例都可獨立使用;處理檔案的範例會建立自己的暫存輸入。
選擇替換方式
如果值已存放在 Bash 變數中,參數展開無須啟動另一個程序即可完成替換。若要對輸入資料流中的各行進行簡單替換,尤其是輸入來自管線時,可使用 sed。如果同一趟處理還需要欄位、記錄或條件邏輯,則可使用 awk。讀取檔案並將結果重新導向至另一個檔案的命令會保留原始檔;原地修改選項會變更來源檔,使用時應格外小心。
範例以字面文字 old 作為搜尋樣式,並以 new 作為替換文字。在 Bash 參數展開中,搜尋文字不會自動視為正規表示式;sed 和 awk 則各有自己的樣式語法。如果輸入或替換文字包含工具使用的分隔符,請為該工具選擇合適的分隔符,並適當跳脫。
使用 Bash 參數展開替換第一個或所有符合項目
Bash 參數展開會以 ${value/pattern/replacement} 替換第一個符合的樣式。在第二個斜線後再加一個斜線,即可替換所有符合項目:${value//pattern/replacement}。這些範例會在區域範圍內設定 value,因此原變數保持不變,替換結果會存入 updated。
執行以下範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
value='red old blue old'
updated="${value/old/new}"
printf '%s\n' "$updated"
red new blue old
若要替換同一個值中的所有符合項目:
value='red old blue old'
updated="${value//old/new}"
printf '%s\n' "$updated"
red new blue new
這裡使用的是 Bash 樣式,因此 * 等字元具有樣式意義。如果樣式來自變數或使用者輸入,請謹慎處理,並適當加上引號。參數展開只會處理單一 shell 值,本身不會修改檔案。
匹配字串尾端的樣式
百分號形式 ${value%pattern} 會移除符合 pattern 的最短尾端部分;${value%%pattern} 則會移除符合的最長尾端部分。它通常不代表在字串中「尋找最後一次出現的位置」。若要替換已知的尾碼,請明確檢查或轉換字串結尾。例如,可以使用符合 .txt 尾端的樣式來替換這個尾碼:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
filename='report.txt'
updated="${filename%.txt}.md"
printf '%s\n' "$updated"
report.md
這項操作只會處理符合的尾端。名稱中較前面位置的 .txt 不會被這個尾端移除樣式選中。如果名稱可能有其他結尾,請先檢查預期的尾碼,再執行轉換,並明確決定如何處理不符合的名稱。
使用 sed 替換資料流和檔案中的文字
sed 的替換形式 s/pattern/replacement/ 會替換每個輸入行中的第一個符合項目。在最後一個分隔符後加上 g,即可替換每一行中的所有符合項目。以下獨立命令會讀取檔案,並將轉換後的文字寫到標準輸出:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
input=$(mktemp)
trap 'rm -f "$input"' EXIT
printf '%s\n' 'old road, old bridge' > "$input"
sed 's/old/new/g' "$input"
new road, new bridge
若要儲存結果並保留原始檔,請將輸出重新導向至不同的檔案,例如 output.txt。不要讓輸入和重新導向的輸出使用相同路徑:shell 會在 sed 讀取前先開啟目的檔,可能因此清空來源檔。-i 選項會原地修改檔案,而備份語法會因 sed 的實作版本而異。請先用副本檢查命令,或改用獨立的輸出檔;若必須原地修改,請先備份。
在 sed 表達式中,/ 是常用的分隔符。如果搜尋或替換文字含有斜線,可以改用 | 等其他分隔符,並跳脫表達式中仍出現的分隔符。sed 樣式使用正規表示式,因此字面標點符號也可能需要跳脫。
使用 awk 逐筆記錄替換
awk 可以在處理每筆輸入記錄時替換其中的符合項目。gsub 會替換所選字串中的所有符合項目,並回傳替換次數;$0 表示目前的記錄。以下命令會印出轉換後的記錄,不會修改輸入檔:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
input=$(mktemp)
trap 'rm -f "$input"' EXIT
printf '%s\n' 'old road, old bridge' > "$input"
awk '{ gsub(/old/, "new", $0); print }' "$input"
new road, new bridge
若要產生轉換後的檔案,請將輸出重新導向至新路徑,並確認目的地與輸入檔不同。awk 適用正規表示式和替換字串規則;若樣式不是固定字面值,就必須妥善處理具有特殊意義的字元。若替換屬於逐筆記錄處理的一部分,請選用 awk;若只是簡單的資料流替換,sed 可能更容易閱讀。
常見陷阱與實用建議
若要替換變數中的多個項目,可以串接參數展開,並將每次結果指定給下一個值;也可以在一次 sed 或 awk 處理中進行多項替換。請明確寫出每個轉換步驟,讓處理順序一目了然:前一次替換可能產生文字,讓後續樣式符合。
如果文字已在 Bash 變數中,且樣式簡單,可使用參數展開。這能省去外部命令,但不應以籠統的效能說法取代對實際工作負載的測量。sed 很適合逐行替換資料流;若替換同時涉及欄位、條件或其他記錄處理,awk 會更實用。這些工具的樣式和替換規則各不相同,不要假設為其中一個工具撰寫的樣式能直接套用到另一個工具。
修改檔案前,請將結果寫入不同的目的檔或先備份,以保留原始內容。確認輸入和輸出路徑,檢查有代表性的輸入;只有確實需要時才原地修改。樣式範圍較廣時尤其要小心,因為全域替換可能影響每一行中的所有符合項目。
先用資料副本執行相關範例,檢查結果後再套用到重要檔案。本文沒有提供這些方法的實測比較,因此請根據輸入型態、轉換需求和檔案安全要求選擇方法,不要依據未經證實的速度排名。