我正在尝试进行基于字典的搜索和替换,但我无法弄清楚如何使其区分大小写/完全匹配,但事实证明这非常困难。
我有三个文件,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
更好?如果是这样,那会去哪里?
希望有人能把我推向正确的方向......
干杯,铌
答案1
我根本不会为此使用paste
and 。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
仅断言我们期望在那里看到的内容,而不实际匹配和使用任何输入文本。零宽度断言的其他示例包括^
(行锚起点)和$
(行锚终点)。Assertions
在同一手册页中搜索。如果您希望将 FileA 中的模式视为固定字符串(即,将所有正则表达式元字符
*
视为?
没有特殊含义的文字字符串),则用\Q
和包围该模式\E
以禁用(引用)元字符。\b
重要的是外部的\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;
-
在这里使用零宽度断言(而不仅仅是像 那样的否定字符类
[^-]
)很重要,它可以防止 RE 吞噬下一个字符(出于同样的原因,零宽度断言\b
实际上不匹配或消耗输入)。同样,这记录在 中man perlre
,搜索Lookaround Assertions
。我也在脚本中添加了这些示例。
未使用修饰符
/i
,因此正则表达式匹配将区分大小写。该脚本具有非常原始的参数处理。如果您需要更好的东西,请使用 perl 的许多命令行参数/选项处理模块之一,例如获取选择::标准或者Getopt::长。这些都是核心 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
就地编辑选项,如果输入来自标准输入而不是文件,该脚本仍然可以工作。