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

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

我正在尝试替换 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 Bfile 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并转义可能出现在这些行上的任何特殊字符,然后加入LHSRHS添加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循环传递它,该循环将迭代每一行,保存fileBas$afileCas的值$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 长,则此脚本会创建大量垃圾文件,反之亦然(没有测试那么远)

相关内容