根据字符串列表和相应替换列表替换文件中的确切字符串,

根据字符串列表和相应替换列表替换文件中的确切字符串,

我正在尝试进行基于字典的搜索和替换,但我无法弄清楚如何使其区分大小写/完全匹配,但事实证明这非常困难。

我有三个文件,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更好?如果是这样,那会去哪里?

希望有人能把我推向正确的方向......

干杯,铌

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

答案1

我根本不会为此使用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仅断言我们期望在那里看到的内容,而不实际匹配和使用任何输入文本。零宽度断言的其他示例包括^(行锚起点)和$(行锚终点)。Assertions在同一手册页中搜索。

  • 如果您希望将 FileA 中的模式视为固定字符串(即,将所有正则表达式元字符*视为?没有特殊含义的文字字符串),则用\Q和包围该模式\E以禁用(引用)元字符。\b重要的是外部\Q\E。我添加了一个注释掉的示例。这也记录在man perlre.

  • 如果 FileA 中的任何模式以未转义\字符结尾,脚本将中断。此外,\E如果您使用固定字符串版本,则任何包含的模式都可能导致它损坏。而且\Q在非固定字符串版本中也会引起问题。垃圾进垃圾出。清理您的输入。

  • 同样在man perlre:perl 将单词字符 ( \w) 定义为:字母数字加“_”,加上其他连接标点符号加 Unicode 标记

  • 连字符和大多数其他标点字符终止单词。 houseboatFileC 中的内容将保持不变,但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就地编辑选项,如果输入来自标准输入而不是文件,该脚本仍然可以工作。

相关内容