Text-Processing

如何替換文件中的字元串?

  • January 4, 2022

根據某些搜尋條件替換文件中的字元串是一項非常常見的任務。我怎樣才能

  • foobar目前目錄中的所有文件替換字元串?
  • 對子目錄做同樣的遞歸?
  • 僅當文件名與另一個字元串匹配時才替換?
  • 僅當在特定上下文中找到字元串時才替換?
  • 如果字元串在某個行號上,則替換?
  • 用相同的替換替換多個字元串
  • 用不同的替換替換多個字元串
  1. 將目前目錄中所有文件中出現的所有字元串替換為另一個字元串:

這些適用於您 知道該目錄僅包含正常文件並且您想要處理所有非隱藏文件的情況。如果不是這種情況,請使用 2 中的方法。

sed此答案中的所有解決方案都假定 GNU sed。如果使用 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

(需要gawk4.1.0 或更高版本)。

  • 對於不同的欄位,只需使用$NwhereN是感興趣欄位的編號。對於不同的欄位分隔符(:在此範例中),請使用:
gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file

另一種解決方案使用perl

   perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo 

注意:awkperl解決方案都將影響文件中的間距(刪除前導和尾隨空格,並將空格序列轉換為匹配行中的一個空格字元)。對於不同的欄位,使用$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'將替換foobaz)。

  • 或 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|' &lt;patterns.txt |
sed -f- ./editfile &gt;outfile

上述格式在很大程度上是任意的,例如,不允許在MATCHREPLACE中使用**<<!>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 &gt;./outfile

POSIXsed將按照它們在命令行中出現的順序將所有腳本連接成一個。這些都不需要以\newline 結尾。

  • grep可以以同樣的方式工作:
sed -e'#generate a pattern list' &lt;in |
grep -f- ./grepped_file
  • 將固定字元串用作模式時,最好對正則表達式元字元進行轉義。你可以很容易地做到這一點:
sed 's/[]$&^*\./[]/\\&/g
     s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
' &lt;patterns.txt |
sed -f- ./editfile &gt;outfile

5.多次替換操作:用同一個字元串替換多個模式

  • 替換任何foo,barbaz``foobar
sed -Ei 's/foo|bar|baz/foobar/g' file
  • 或者
perl -i -pe 's/foo|bar|baz/foobar/g' file

引用自:https://unix.stackexchange.com/questions/112023