Sed

根據字元串列表和相應替換列表替換文件中的確切字元串,

  • May 3, 2022

我正在嘗試進行基於字典的搜尋和替換,但我無法弄清楚如何使其區分大小寫/完全匹配,但事實證明這非常困難。

我有三個文件,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

它基於此: https ://unix.stackexchange.com/a/271108

我根本不會使用pasteand sed。我會使用 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禁用(引用)元字元圍繞模式。重要的是\bs在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::StdGetopt::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就地編輯選項,如果輸入來自標準輸入而不是文件,這個腳本仍然可以工作。

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