我有一个文件,其中包含替换对的列表(大约100个),sed
用它来替换文件中的字符串。
这对对象如下:
old|new
tobereplaced|replacement
(stuffiwant).*(too)|\1\2
我目前的代码是:
cat replacement_list | while read i
do
old=$(echo "$i" | awk -F'|' '{print $1}') #due to the need for extended regex
new=$(echo "$i" | awk -F'|' '{print $2}')
sed -r "s/`echo "$old"`/`echo "$new"`/g" -i file
done
我不禁想到有更优化的方式来执行替换。我尝试先转动循环以首先运行文件的行,但结果却要贵得多。
还有其他加速此脚本的方法吗?
修改
感谢所有快速回复。在选择答案之前,让我尝试各种建议。
要清理的一件事:我还需要子表达式/组功能。例如,我可能需要的一个替换是:
([0-9])U|\10 #the extra brackets and escapes were required for my original code
有关改进的一些细节(待更新):
cut
代替awk
:0.71s 答案 0 :(得分:8)
您可以使用sed
生成格式正确的sed
输入:
sed -e 's/^/s|/; s/$/|g/' replacement_list | sed -r -f - file
答案 1 :(得分:3)
我最近对各种字符串替换方法进行了基准测试,其中包括自定义程序sed -e
,perl -lnpe
以及可能不是广为人知的MySQL命令行实用程序replace
。针对字符串替换进行优化的replace
几乎比sed
快一个数量级。结果看起来像这样(最慢):
custom program > sed > LANG=C sed > perl > LANG=C perl > replace
如果您想要表现,请使用replace
。要让它在您的系统上可用,您需要安装一些MySQL发行版。
来自replace.c:
替换文本文件中的字符串
此程序替换文件中的字符串或stdin转换为stdout。它接受from-string / to-string对的列表,并用相应的to-string替换每个出现的from-string。匹配找到的字符串的第一次出现。如果要替换字符串的可能性不止一个,则在较短的匹配之前首选较长的匹配。
...
程序生成字符串的DFA状态机,速度不依赖于替换字符串的数量(仅取代数量)。假设一行以\ n或\ 0结尾。字符串长度没有限制exept内存。
更多关于sed。您可以将多个内核与sed结合使用,方法是将替换内容拆分为#cpus组,然后通过sed
命令对其进行管道处理,如下所示:
$ sed -e 's/A/B/g; ...' file.txt | \
sed -e 's/B/C/g; ...' | \
sed -e 's/C/D/g; ...' | \
sed -e 's/D/E/g; ...' > out
此外,如果您使用sed
或perl
并且您的系统设置了UTF-8,那么它还会提高性能,以便在命令前放置LANG=C
:
$ LANG=C sed ...
答案 2 :(得分:1)
您可以减少不必要的awk调用,并使用BASH来破坏名称 - 值对:
while IFS='|' read -r old new; do
# echo "$old :: $new"
sed -i "s~$old~$new~g" file
done < replacement_list
IFS =&#39; |&#39;将允许启用读取以在2个不同的shell变量old
和new
中填充名称值。
这假设您的名称 - 值对中不存在~
。如果不是这种情况,那么可以随意使用备用的sed分隔符。
答案 3 :(得分:1)
以下是我要尝试的内容:
sed
搜索替换对存储在Bash数组中,例如; patterns=(
old new
tobereplaced replacement
)
pattern_count=${#patterns[*]} # number of pattern
sedArgs=() # will hold the list of sed arguments
for (( i=0 ; i<$pattern_count ; i=i+2 )); do # don't need to loop on the replacement…
search=${patterns[i]};
replace=${patterns[i+1]}; # … here we got the replacement part
sedArgs+=" -e s/$search/$replace/g"
done
sed ${sedArgs[@]} file
此命令中的结果:
sed -e s / old / new / g -e s / tobereplaced / replacement / g file
答案 4 :(得分:0)
你可以试试这个。
pattern=''
cat replacement_list | while read i
do
old=$(echo "$i" | awk -F'|' '{print $1}') #due to the need for extended regex
new=$(echo "$i" | awk -F'|' '{print $2}')
pattern=${pattern}"s/${old}/${new}/g;"
done
sed -r ${pattern} -i file
这将仅对包含所有替换的文件运行sed命令一次。您可能还想将awk
替换为cut
。 cut
可能比awk
更优化,但我不确定。
old=`echo $i | cut -d"|" -f1`
new=`echo $i | cut -d"|" -f2`
答案 5 :(得分:0)
您可能想要在awk中完成整个事情:
awk -F\| 'NR==FNR{old[++n]=$1;new[n]=$2;next}{for(i=1;i<=n;++i)gsub(old[i],new[i])}1' replacement_list file
从第一个文件中构建旧词和新词的列表。 next
确保脚本的其余部分不在第一个文件上运行。对于第二个文件,循环遍历替换列表并逐个执行它们。最后的1
表示该行已打印。
答案 6 :(得分:0)
{ cat replacement_list;echo "-End-"; cat YourFile; } | sed -n '1,/-End-/ s/$/³/;1h;1!H;$ {g
t again
:again
/^-End-³\n/ {s///;b done
}
s/^\([^|]*\)|\([^³]*\)³\(\n\)\(.*\)\1/\1|\2³\3\4\2/
t again
s/^[^³]*³\n//
t again
:done
p
}'
更多通过sed编写代码的乐趣。尝试一段时间的性能,因为这只会启动1 sed,即recursif。
用于posix sed(所以--posix
使用GNU sed)
<强>的解释强>
³
的行和带有-End-
的列表),以便更轻松地处理sed(在posix sed中的类字符中难以使用\ n -End-³
,请删除该行并转到最终打印t again
)t again
)。需要T,因为b
未重置测试,而下一个t
始终为真。答案 7 :(得分:0)
感谢上面的@miku;
我有一个 100MB 的文件,其中包含 80k 个替换字符串的列表。
我尝试了 sed 顺序或并行的各种组合,但没有看到吞吐量比大约 20 小时的运行时间更短。
相反,我将我的列表放入一系列脚本中,例如“cat in | replace aold anew bold bnew Cold cnew ... > out ; rm in ; mv out in”。
我随机为每个文件挑选了 1000 个替换,所以一切都是这样的:
# first, split my replace-list into manageable chunks (89 files in this case)
split -a 4 -l 1000 80kReplacePairs rep_
# next, make a 'replace' script out of each chunk
for F in rep_* ; do \
echo "create and make executable a scriptfile" ; \
echo '#!/bin/sh' > run_$F.sh ; chmod +x run_$F.sh ; \
echo "for each chunk-file line, strip line-ends," ; \
echo "then with sed, turn '{long list}' into 'cat in | {long list}' > out" ; \
cat $F | tr '\n' ' ' | sed 's/^/cat in | replace /;s/$/ > out/' >> run_$F.sh ;
echo "and append commands to switch in and out files, for next script" ; \
echo -e " && \\\\ \nrm in && mv out in\n" >> run_$F.sh ; \
done
# put all the replace-scripts in sequence into a main script
ls ./run_rep_aa* > allrun.sh
# make it executable
chmod +x allrun.sh
# run it
nohup ./allrun.sh &
.. 运行时间不到 5 分钟,远少于 20 小时!
回想起来,我本可以在每个脚本中使用更多对,通过找出构成限制的行数。
xargs --show-limits </dev/null 2>&1 | grep --color=always "actually use:"
Maximum length of command we could actually use: 2090490
所以不到 2MB;我的脚本有多少对?
head -c 2090490 80kReplacePairs | wc -l
76923
所以看起来我可以使用 2 * 40000 行块