我有三个文件,main.txt
,out.txt
和in.txt
.我想用 的内容替换每次出现的out.txt
inmain.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.txt
in.txt
main.txt
$_
s/pattern/replacement/flags
是运算符默认处理的变量print
,相当于模式空间在sed
。
这里的模式是导致内部内容被按字面处理,而不是作为正则表达式处理\Q$out\E
的地方。\Q...\E
该g
标志将替换所有出现的情况,如 中的情况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 支持<()
(如zsh
、ksh
、bash
),您可以在文件之间插入标记(此处: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.txt
为in.txt
tL
只要可以更换就可以循环s/.*MARK\n//
打印前删除其他文件
请注意限制:
- 显然,您需要选择一个
MARK
不属于文本的部分。 - 正如所评论的,这对于大文件可能会失败,具体取决于您的
sed
实现。我从未在现代系统中触及过这个边界,但它确实存在。刷新不匹配的行可以解决这个问题,但这开始是编程,这不是目的sed
。