我正在尝试替换 a 中的字符串file A
:
Hello Peter, how is your dad? where is mom?
其中要替换的字符串位于file B
:
Peter
dad
mom
及其相应的替代品位于file C
:
John
wife
grandpa
预期结果:
Hello John, how is your wife? where is grandpa?
我可以编辑并使用 中相应行的值file A
替换 中的值吗?file B
file C
到目前为止我所做的:
cat 1.txt | sed -e "s/$(sed 's:/:\\/:g' 2.txt)/$(sed 's:/:\\/:g' 3.txt)/" > 4.txt
file B
如果&中只有一行,则有效file C
,如果有多于一行,则无效。
答案1
最简单的方法sed
是处理这两个列表并将它们变成一个脚本文件例如
s/line1-from-fileB/line1-from-fileC/g
s/line2-from-fileB/line2-from-fileC/g
....................................
s/lineN-from-fileB/lineN-from-fileC/g
然后sed
将执行、编辑fileA
.这恰当的方法是首先处理LHS
/RHS
并转义可能出现在这些行上的任何特殊字符,然后加入LHS
并RHS
添加s
、分隔符/
和g
(例如 with paste
)并将结果通过管道传输到sed
:
paste -ds///g /dev/null /dev/null \
<(sed 's|[[\.*^$/]|\\&|g' fileB) <(sed 's|[\&/]|\\&|g' fileC) \
/dev/null /dev/null | sed -f - fileA
所以它是:一paste
和三sed
将只处理每个文件一次,无论行数如何。
这假设您的 shell 支持进程替换并且您sed
可以读取脚本文件从标准输入。另外,它不会就地编辑(我省略了开关,-i
因为并非所有sed
版本都支持它)
答案2
如果您希望替换彼此独立完成,例如:
foo -> bar
bar -> foo
应用于
foobar
以导致:
barfoo
与foofoo
天真的s/foo/bar/g; s/bar/foo/g
翻译相反,你可以这样做:
perl -pe '
BEGIN{
open STRINGS, "<", shift@ARGV or die"STRINGS: $!";
open REPLACEMENTS, "<", shift@ARGV or die "REPLACEMENTS: $!";
while (defined($a=<STRINGS>) and defined($b=<REPLACEMENTS>)) {
chomp ($a, $b);
push @repl, $b;
push @re, "$a(?{\$repl=\$repl[" . $i++. "]})"
}
eval q($re = qr{) . join("|", @re) . "}";
}
s/$re/$repl/g' strings.txt replacements.txt fileA
这是.perl
中预期的正则表达式patterns.txt
。由于 Perl 正则表达式可以执行任意代码,因此对它们进行清理非常重要。如果只想替换固定字符串,可以将其更改为:
perl -pe '
BEGIN{
open PATTERNS, "<", shift@ARGV or die"PATTERNS: $!";
open REPLACEMENTS, "<", shift@ARGV or die "REPLACEMENTS: $!";
for ($i = 0; defined($a=<PATTERNS>) and defined($b=<REPLACEMENTS>); $i++) {
chomp ($a, $b);
push @string, $a;
push @repl, $b;
push @re, "\\Q\$string[$i]\\E(?{\$repl=\$repl[$i]})"
}
eval q($re = qr{) . join("|", @re) . "}";
}
s/$re/$repl/g' patterns.txt replacements.txt fileA
答案3
在这个简单的示例中,您显示每个目标单词在文件中只出现一次,您可以简单地执行以下操作:
$ paste fileB fileC | while read a b; do sed -i "s/$a/$b/" fileA; done
$ cat fileA
Hello John, how is your wife? where is grandpa?
该paste
命令将打印两个文件组合中的数据:
$ paste fileB fileC
Peter John
dad wife
mom grandpa
我们通过一个简单的while read
循环传递它,该循环将迭代每一行,保存fileB
as$a
和fileC
as的值$b
。然后,该命令将用sed
替换第一次出现的。如此重复三次。$a
$b
如果您知道您的目标单词仅在文件中出现一次(它们必须出现,否则,您需要提供更多详细信息,我们可以使用这些详细信息来确定应替换哪个单词)并且您的文件很小,则此方法很好,就像你所展示的那样。对于较大的文件,这将花费很长时间并且效率非常低,因为它需要为每对单词运行一次。
因此,如果您有更大的文件,您可能需要这样的东西:
paste fileB fileC |
perl -lane '$words{$F[0]}=$F[1]}
END{open(A,"fileA"); while(<A>){s/$_/$words{$_}/ for keys %words; print}'
答案4
我创建的解决方案不是很短,但足够简单,非常易读。除非你的任务是用 sed 完成整个事情...?
#!/usr/bin/bash
cp A.txt D.txt
x=1
length=$(wc -l B.txt | sed 's/\ .*//g')
until [ $x -eq $length ]; do
Bx=$(awk "NR==$x" B.txt)
Cx=$(awk "NR==$x" C.txt)
sed -i "s/$Bx/$Cx/g" D.txt
x=$(($x+1))
done
rm -f ./sed*
请注意,如果 B.txt 比 C.txt 长,则此脚本会创建大量垃圾文件,反之亦然(没有测试那么远)