根據字元串列表和相應替換列表替換文件中的確切字元串,
我正在嘗試進行基於字典的搜尋和替換,但我無法弄清楚如何使其區分大小寫/完全匹配,但事實證明這非常困難。
我有三個文件,fileA 是要編輯的文本,FileB 是要搜尋的單詞列表,FileC 是要替換的單詞列表。
paste -ds///g /dev/null /dev/null <(sed 's|[[\.*^\b$\b/]|\\&|g' fileB) <(sed 's|[\&/]|\\\b&\b|g' fileC) /dev/null /dev/null | sed -f - fileA
據我所知,為了讓 sed 搜尋和替換完全匹配,我需要做類似的事情
sed 's/\<exact_word_to_replace\>/exact_replacement/g' filename
但我真的不知道在我上面的程式碼中,
\<
and\>
應該去哪裡!會
\b
更好嗎?如果是這樣,那會去哪裡?希望有人能在這裡把我推向正確的方向……
乾杯,NB
我根本不會使用
paste
andsed
。我會使用 awk 或 perl。例如:首先,一些範例輸入文件。請注意(為了我自己的方便)我已經改變了
File[ABC]
- 文件 A 和 B 是搜尋模式和相應的替換。FileC 是要修改的輸入文本文件。重要的是包含搜尋詞的文件是腳本的第一個參數,包含替換字元串的文件是第二個參數。要修改的實際輸入來自第三個(以及後續,如果有的話)參數和/或標準輸入。
$ cat FileA house $ cat FileB dwelling $ cat FileC Mr House does not live in a land-based house, his house is a houseboat.
還有一個 perl 腳本。將其另存為,
replace.pl
並使其可執行chmod +x replace.pl
:$ cat replace.pl #!/usr/bin/perl use strict; # Variables to hold the first two filenames. my $FileA = shift; my $FileB = shift; # An associative array ("hash") called %RE. The keys are the search # regexes and the values are the replacements. my %RE; # Read both FileA and FileB at the same time, to build a # hash of pre-compiled regular expressions (%RE) and their # replacements. open(my $A,'<',$FileA) || die "Couldn't open $FileA for read: $!\n"; open(my $B,'<',$FileB) || die "Couldn't open $FileB for read: $!\n"; while(my $a = <$A>) { # loop reading lines from first file die "$FileA is longer than $FileB" if (eof $B); my $b = <$B>; # read in a line from 2nd file die "$FileB is longer than $FileA" if (eof $A && ! eof $B); chomp($a,$b); # Uncomment only ONE of the following four lines: $RE{qr/\b$a\b/} = $b; # regular expression match #$RE{qr/\b\Q$a\E\b/} = $b; # exact-match version. #$RE{qr/(?<!-)\b$a\b(?!-)/} = $b; # regexp match, no hyphen allowed #$RE{qr/(?<!-)\b\Q$a\E\b(?!-)/} = $b; # exact match, no hyphen allowed. } close($A); close($B); # process stdin and/or any remaining filename argument(s) on # the command line (e.g. FileC). while (<>) { foreach my $a (keys %RE) { s/$a/$RE{$a}/g; }; print; }
筆記:
- perl 的
chomp
函式從變數或變數列表中刪除尾隨輸入記錄分隔符($/
- 行尾字元,例如換行符或 CR+LF,具體取決於文本文件類型和作業系統)。見perldoc -f chomp
。- perl 的
qr
引用運算符返回一個已編譯的正則表達式。詳情請參閱perldoc -f qr
。- 如果搜尋、替換和文本文件都很小,則預編譯正則表達式幾乎沒有區別。如果搜尋和替換列表(文件 A 和 B)很長和/或輸入(文件 C)很大,則會對性能產生巨大影響。多次重複編譯正則表達式的成本將增加 CPU 處理能力和時間的大量消耗。
- 正則表達式是從 編譯的
\b$a\b
,因此包含來自 FileA 的值周圍的零寬度字邊界標記。查看man perlre
並蒐索word boundary
。“零寬度”意味著\b
只斷言我們期望在那裡看到的內容,而不實際匹配和消耗任何輸入文本。零寬度斷言的其他範例包括^
(start of line anchor) 和$
(end of line anchor)。Assertions
在同一手冊頁中搜尋。- 如果您希望將 FileA 中的模式視為固定字元串(即,將所有正則表達式元字元視為
*
或?
視為沒有特殊含義的文字字元串),則使用\Q
和\E
禁用(引用)元字元圍繞模式。重要的是\b
s在and之外。我添加了一個註釋掉的例子。這也記錄在.\Q``\E``man perlre
- 如果 FileA 中的任何模式以未轉義
\
字元結尾,則腳本將中斷。此外,\E
如果您使用固定字元串版本,任何包含的模式都可能導致其中斷。並且\Q
在非固定字元串版本中也會造成問題。垃圾進垃圾出。清理您的輸入。- 同樣在
man perlre
:perl 將單詞字元 (\w
) 定義為:字母數字加“_”,再加上其他連接符標點字元加 Unicode 標記- 連字元和大多數其他標點符號終止單詞。
houseboat
在 FileC 中將保持不變,但house-boat
將更改為dwelling-boat
,並將share-house
更改為share-dwelling
. 這不太理想。這可以通過更改腳本以對 RE 中的連字元使用零寬度的負前瞻和後瞻斷言(
(?!pattern)
和分別)來解決 - 例如或。簡而言之,這些告訴 perl 的正則表達式引擎“如果我們正在尋找的模式之前或之後存在,則不匹配”。(?<!pattern)``$RE{qr/(?<!-)\b$a\b(?!-)/} = $b;``$RE{qr/(?<!-)\b\Q$a\E\b(?!-)/} = $b;``-
在此處使用零寬度
[^-]
斷言(而不僅僅是像\b
輸入)。同樣,這記錄在man perlre
,搜尋Lookaround Assertions
。我也在腳本中添加了這些範例。
- 不使用
/i
修飾符,因此正則表達式匹配將區分大小寫。- 這個腳本有非常原始的參數處理。如果您需要更好的東西,請使用 perl 的許多命令行參數/選項處理模組之一,例如Getopt::Std或Getopt::Long。這些都是核心 perl 模組,並且包含在 perl 中。
最後,一些範例輸出:
$ ./replace.pl FileA FileB FileC Mr House does not live in a land-based dwelling, his dwelling is a houseboat.
如果您希望腳本實際更改每個單獨的輸入文件(而不僅僅是將其/它們列印到標準輸出),請將第一行更改為:
#!/usr/bin/perl
至
#!/usr/bin/perl -i
或(如果您希望將原始文件保存為 .bak):
#!/usr/bin/perl -i.bak
順便說一句,即使使用
-i
就地編輯選項,如果輸入來自標準輸入而不是文件,這個腳本仍然可以工作。