如何在第三个文件中用 in.txt 替换 out.txt 的内容?

如何在第三个文件中用 in.txt 替换 out.txt 的内容?

我有三个文件,main.txtout.txtin.txt.我想用 的内容替换每次出现的out.txtinmain.txt内容in.txt

out.txt和都in.txt可以包含多行和各种特殊字符。如何正确读取和转义这些字符串?

这是一个包含一些边缘情况的示例,例如特殊字符、重复匹配、不完整匹配、重叠匹配。

main.txt:

foo
Replace these
three lines
with some $.*\'"& in it
bar
Replace these
three lines
with some $.*\'"& in it
Replace these
three lines
with some $.*\'"& in it

three lines
Replace these
three lines
three lines
with some $.*\'"& in it
baz

out.txt:

Replace these
three lines
with some $.*\'"& in it

in.txt:

Replacement lines
also with $.*\'"&

预期输出:

foo
Replacement lines
also with $.*\'"&
bar
Replacement lines
also with $.*\'"&
Replacement lines
also with $.*\'"&

three lines
Replace these
three lines
three lines
with some $.*\'"& in it
baz

答案1

perl

perl -0777 -e '$out = <>; $in = <>; $_ = <>; s/\Q$out\E/$in/g; print
              ' out.txt in.txt main.txt > new-main.txt

应该适用于文件可能包含的任何字符或非字符(也适用于二进制文件),只要它们足够小以适合内存。

-0777将输入记录分隔符设置为不可能的值,与执行相同$/ = undef,因此它<>依次从作为参数传递的文件中读取整个文件。

所以我们有$out//分别包含$in、和的$_完整内容。out.txtin.txtmain.txt

$_s/pattern/replacement/flags是运算符默认处理的变量print,相当于模式空间sed

这里的模式是导致内部内容被按字面处理,而不是作为正则表达式处理\Q$out\E的地方。\Q...\Eg标志将替换所有出现的情况,如 中的情况sed


或命令输出,如ls|,请<<>>改为使用那些仅被解释为文件路径的命令输出

答案2

在每个 Unix 机器上的任何 shell 中使用任何 awk:

$ cat tst.awk
FILENAME == ARGV[1] { old = old $0 ORS }
FILENAME == ARGV[2] { new = new $0 ORS }
FILENAME == ARGV[3] { rec = rec $0 ORS }
END {
    lgth = length(old)
    if ( lgth > 0 ) {
        while ( beg = index(rec,old) ) {
            printf "%s%s", substr(rec,1,beg-1), new
            rec = substr(rec,beg+lgth)
        }
    }
    printf "%s", rec
}

$ awk -f tst.awk out.txt in.txt main.txt
foo
Replacement lines
also with $.*\'"&
bar
Replacement lines
also with $.*\'"&
Replacement lines
also with $.*\'"&

three lines
Replace these
three lines
three lines
with some $.*\'"& in it
baz

上面是进行文字字符串匹配和替换,因此它适用于输入文件中的任何字符。

答案3

如果您的 shell 支持<()(如zshkshbash),您可以在文件之间插入标记(此处:MARK)以分隔它们并使用任何 POSIX sed

sed -e 'H;1h;$!d;x;:L
        s/^\(.*\)\(MARK\n\)\(.*\)\2\(.*\)\1/\1\2\3\2\4\3/;tL
        s/.*MARK\n//' out.txt <(echo MARK) in.txt <(echo MARK) main.txt
  • H;1h;$!d;x是一次处理整个文件的典型模式
  • :L开始一个循环
  • s/^\(.*\)\(MARK\n\)\(.*\)\2\(.*\)\1/\1\2\3\2\4\3/替换out.txtin.txt
  • tL只要可以更换就可以循环
  • s/.*MARK\n//打印前删除其他文件

请注意限制:

  1. 显然,您需要选择一个MARK不属于文本的部分。
  2. 正如所评论的,这对于大文件可能会失败,具体取决于您的sed实现。我从未在现代系统中触及过这个边界,但它确实存在。刷新不匹配的行可以解决这个问题,但这开始是编程,这不是目的sed

相关内容