如何替換文件中的字元串?
根據某些搜尋條件替換文件中的字元串是一項非常常見的任務。我怎樣才能
foo
用bar
目前目錄中的所有文件替換字元串?- 對子目錄做同樣的遞歸?
- 僅當文件名與另一個字元串匹配時才替換?
- 僅當在特定上下文中找到字元串時才替換?
- 如果字元串在某個行號上,則替換?
- 用相同的替換替換多個字元串
- 用不同的替換替換多個字元串
- 將目前目錄中所有文件中出現的所有字元串替換為另一個字元串:
這些適用於您 知道該目錄僅包含正常文件並且您想要處理所有非隱藏文件的情況。如果不是這種情況,請使用 2 中的方法。
sed
此答案中的所有解決方案都假定 GNUsed
。如果使用 FreeBSD 或 macOS,請替換-i
為-i ''
. 另請注意,將-i
開關與任何版本的 一起使用sed
具有一定的文件系統安全隱患,並且在您計劃以任何方式分發的任何腳本中都是不可取的。
- 非遞歸,僅此目錄中的文件:
sed -i -- 's/foo/bar/g' * perl -i -pe 's/foo/bar/g' ./*
(對於以空格
perl
結尾的文件名將失敗|
) )。
- 此目錄和所有子目錄中的遞歸正常文件(包括隱藏文件)
find . -type f -exec sed -i 's/foo/bar/g' {} +
如果您使用的是 zsh:
sed -i -- 's/foo/bar/g' **/*(D.)
(如果列表太大,可能會失敗,請參閱
zargs
解決方法)。Bash 不能直接檢查正常文件,需要循環(大括號避免全域設置選項):
( shopt -s globstar dotglob; for file in **; do if [[ -f $file ]] && [[ -w $file ]]; then sed -i -- 's/foo/bar/g' "$file" fi done )
當文件是實際文件 (-f) 並且它們是可寫的 (-w) 時,將選擇這些文件。
2.僅當文件名與另一個字元串匹配/具有特定副檔名/屬於某種類型等時才替換:
- 非遞歸,僅此目錄中的文件:
sed -i -- 's/foo/bar/g' *baz* ## all files whose name contains baz sed -i -- 's/foo/bar/g' *.baz ## files ending in .baz
- 此目錄和所有子目錄中的遞歸正常文件
find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
如果您使用的是 bash(大括號避免全域設置選項):
( shopt -s globstar dotglob sed -i -- 's/foo/bar/g' **baz* sed -i -- 's/foo/bar/g' **.baz )
如果您使用的是 zsh:
sed -i -- 's/foo/bar/g' **/*baz*(D.) sed -i -- 's/foo/bar/g' **/*.baz(D.)
--
用於告訴命令行sed
中將不再提供標誌。這對於防止以 . 開頭的文件名很有用-
。
- 如果文件屬於某種類型,例如執行檔(
man find
更多選項請參閱):find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
zsh
:sed -i -- 's/foo/bar/g' **/*(D*)
3.僅當在特定上下文中找到字元串時才替換
- 僅當同一行有後面時才替換
foo
為:bar``baz
sed -i 's/foo\(.*baz\)/bar\1/' file
在
sed
, using\( \)
保存括號中的任何內容,然後您可以使用\1
. 這個主題有很多變體,要了解有關此類正則表達式的更多資訊,請參見此處。
- 僅當在輸入文件的 3d 列(欄位)上找到時才替換
foo
為(假設空格分隔的欄位):bar``foo
gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
(需要
gawk
4.1.0 或更高版本)。
- 對於不同的欄位,只需使用
$N
whereN
是感興趣欄位的編號。對於不同的欄位分隔符(:
在此範例中),請使用:gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
另一種解決方案使用
perl
:perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo
注意:
awk
和perl
解決方案都將影響文件中的間距(刪除前導和尾隨空格,並將空格序列轉換為匹配行中的一個空格字元)。對於不同的欄位,使用$F[N-1]
whereN
是您想要的欄位編號並使用不同的欄位分隔符($"=":"
將輸出欄位分隔符設置為:
):perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
- 僅在第 4 行替換
foo
為:bar
sed -i '4s/foo/bar/g' file gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file perl -i -pe 's/foo/bar/g if $.==4' file
4、多次替換操作:用不同的字元串替換
- 您可以組合
sed
命令:sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
請注意,訂單很重要(
sed 's/foo/bar/g; s/bar/baz/g'
將替換foo
為baz
)。
- 或 Perl 命令
perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
- 如果您有大量模式,將模式及其替換保存在
sed
腳本文件中會更容易:#! /usr/bin/sed -f s/foo/bar/g s/baz/zab/g
- 或者,如果您的模式對太多而無法實現上述操作,則可以從文件中讀取模式對(兩個空格分隔的模式, $ pattern and $ 替換,每行):
while read -r pattern replacement; do sed -i "s/$pattern/$replacement/" file done < patterns.txt
- 對於長模式列表和大型數據文件,這將非常慢,因此您可能希望讀取模式並
sed
從中創建腳本。以下假設*<<!>space<!>>分隔符分隔文件中每行出現一個MATCH<<!>space<!>>REPLACE**patterns.txt
*對的列表:sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <patterns.txt | sed -f- ./editfile >outfile
上述格式在很大程度上是任意的,例如,不允許在MATCH或REPLACE中使用**<<!>space<!>>。該方法非常通用:基本上,如果您可以創建一個看起來像腳本的輸出流,那麼您可以通過將’s script file指定為stdin.
sed``sed``sed``-
- 您可以以類似的方式組合和連接多個腳本:
SOME_PIPELINE | sed -e'#some expression script' \ -f./script_file -f- \ -e'#more inline expressions' \ ./actual_edit_file >./outfile
POSIX
sed
將按照它們在命令行中出現的順序將所有腳本連接成一個。這些都不需要以\n
ewline 結尾。
grep
可以以同樣的方式工作:sed -e'#generate a pattern list' <in | grep -f- ./grepped_file
- 將固定字元串用作模式時,最好對正則表達式元字元進行轉義。你可以很容易地做到這一點:
sed 's/[]$&^*\./[]/\\&/g s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g| ' <patterns.txt | sed -f- ./editfile >outfile
5.多次替換操作:用同一個字元串替換多個模式
- 替換任何
foo
,bar
或baz``foobar
sed -Ei 's/foo|bar|baz/foobar/g' file
- 或者
perl -i -pe 's/foo|bar|baz/foobar/g' file