逐行应用字符串替换
Apply string substitutions line by line
我需要在文本文件上应用一系列替换,使用具有相同行数的过滤器文件:过滤器的第 n
行应应用于原始文件的第 n
行文件。
例如原始文件:
foo
bar
foobar
过滤文件:
s/oo/uu/
s/a/i/
s/b/l/
预期结果:
fuu
bir
foolar
由于sed
会在每一行应用每个过滤器,使用sed -f filterfile
效率特别低(行数相当多,所以N²
也很大......) .此外,虽然在我的特定情况下我可以修改过滤器来避免这个问题,但这个命令会导致示例的错误结果。
我目前正在实施以下方法(仍在尝试解决表格问题……):
paste -d'@' filterA filterB infile \
|while IFS="@" read AA BB LINE;
do
echo $LINE|"s/$AA/$BB/g"
done > outfile
但我想知道是否有更优雅的解决方案,例如一些 sed
选项? (最好使用标准 GNU/Linux 工具。)
awk -F'/' '
NR==FNR {
old[NR] =
new[NR] =
next
}
{ gsub(old[FNR],new[FNR]) }
1' filterfile originalfile
fuu
bir
foolar
以上内容在任何 UNIX 机器上使用任何 shell 中的任何 awk 都可以工作。
您可以通过在每行前面添加正确的行地址来修改您的过滤器文件
$ nl filter
1 s/oo/uu/
2 s/a/i/
3 s/b/l/
然后将其通过管道传递给 sed:
$ nl filter | sed -f- infile
fuu
bir
foolar
如果需要全局替换,请先附加 g
:
$ sed 's/$/g/' filter
s/oo/uu/g
s/a/i/g
s/b/l/g
导致
sed 's/$/g/' filter | nl | sed -f- infile
替换后开始下一个循环的一个小优化是在其后添加一个b
命令:
sed 's/.*/{&g;b}/' filter | nl | sed -f- infile
这会立即开始下一个循环。问题中输入和过滤文件的 30,000 行版本的效果是节省了大约 20% 的时间:
$ wc -l filter infile
33033 filter
33033 infile
66066 total
$ time sed 's/$/g/' filter | nl | sed -f- infile >/dev/null
real 0m15.868s
user 0m15.522s
sys 0m0.296s
$ time sed 's/.*/{&g;b}/' filter | nl | sed -f- infile >/dev/null
real 0m12.238s
user 0m11.901s
sys 0m0.271s
如果您的文件很大,awk 会快很多(代码由 Ed Morton 提供):
$ time awk 'NR==FNR{o[NR]=;n[NR]=;next} {gsub(o[FNR],n[FNR])} 1' filter infile >/dev/null
real 0m0.073s
user 0m0.061s
sys 0m0.007s
我需要在文本文件上应用一系列替换,使用具有相同行数的过滤器文件:过滤器的第 n
行应应用于原始文件的第 n
行文件。
例如原始文件:
foo
bar
foobar
过滤文件:
s/oo/uu/
s/a/i/
s/b/l/
预期结果:
fuu
bir
foolar
由于sed
会在每一行应用每个过滤器,使用sed -f filterfile
效率特别低(行数相当多,所以N²
也很大......) .此外,虽然在我的特定情况下我可以修改过滤器来避免这个问题,但这个命令会导致示例的错误结果。
我目前正在实施以下方法(仍在尝试解决表格问题……):
paste -d'@' filterA filterB infile \
|while IFS="@" read AA BB LINE;
do
echo $LINE|"s/$AA/$BB/g"
done > outfile
但我想知道是否有更优雅的解决方案,例如一些 sed
选项? (最好使用标准 GNU/Linux 工具。)
awk -F'/' '
NR==FNR {
old[NR] =
new[NR] =
next
}
{ gsub(old[FNR],new[FNR]) }
1' filterfile originalfile
fuu
bir
foolar
以上内容在任何 UNIX 机器上使用任何 shell 中的任何 awk 都可以工作。
您可以通过在每行前面添加正确的行地址来修改您的过滤器文件
$ nl filter
1 s/oo/uu/
2 s/a/i/
3 s/b/l/
然后将其通过管道传递给 sed:
$ nl filter | sed -f- infile
fuu
bir
foolar
如果需要全局替换,请先附加 g
:
$ sed 's/$/g/' filter
s/oo/uu/g
s/a/i/g
s/b/l/g
导致
sed 's/$/g/' filter | nl | sed -f- infile
替换后开始下一个循环的一个小优化是在其后添加一个b
命令:
sed 's/.*/{&g;b}/' filter | nl | sed -f- infile
这会立即开始下一个循环。问题中输入和过滤文件的 30,000 行版本的效果是节省了大约 20% 的时间:
$ wc -l filter infile
33033 filter
33033 infile
66066 total
$ time sed 's/$/g/' filter | nl | sed -f- infile >/dev/null
real 0m15.868s
user 0m15.522s
sys 0m0.296s
$ time sed 's/.*/{&g;b}/' filter | nl | sed -f- infile >/dev/null
real 0m12.238s
user 0m11.901s
sys 0m0.271s
如果您的文件很大,awk 会快很多(代码由 Ed Morton 提供):
$ time awk 'NR==FNR{o[NR]=;n[NR]=;next} {gsub(o[FNR],n[FNR])} 1' filter infile >/dev/null
real 0m0.073s
user 0m0.061s
sys 0m0.007s