我有一个 awk 脚本,可以处理非常大的文件,如下所示:
K1353 SF3987.7PD833391.4 KARE
K1353 SF3987.2KD832231.4 MEAKE
K1332 IF4987.7RP832231.2 LEAOS
K1329 SF2787.7KD362619.3 NEDLE
K1353 SK3K84.3KD832231.3 PQAKM
该文件是固定列文件。
该脚本当前在一些提取的字段上运行一个程序,并将它们替换回原来的位置 – 我正在使用 awk。性能不如更简单的awk脚本;瓶颈似乎是命令的系统调用。
出于演示目的,我刚刚包含了“rev”,但它实际上运行了一个翻译这些字段的自定义程序。该命令通常运行速度非常快,尽管仅通过 STDIN 接受两个参数或可以从文件中读取。真正的执行程序是第三方应用程序/二进制文件,我不知道它如何工作的细节。
BEGIN {
csmok="rev"
}
{
type = substr($0,1,1)
if (type == "K") {
RX=substr($0,6,9)
RY=substr($0,15,9)
cmd=sprintf("echo %s %s | %s", RX, RY, csmok)
cmd | getline output
close(cmd)
split(output,k," ")
sub(RX,k[1])
sub(RY,k[2])
print
}
}
并像这样运行:
$ awk -f process.awk file.dat
我处理的文件有时很大 – 900,000 行 – 这需要很长时间才能执行。缓慢是指它爆发到 system()/exec 调用。
我将如何改善运行时间?
我考虑尝试以某种方式使脚本执行一次,就像将所有提取的字段连接到一个命令中一样:
echo -e "SF3987.7 PD833391.4\nSF3987.2 KD832231.4\nIF4987.7 RP832231.2" | rev
或者
rev << EOF
SF3987.7 PD833391.4
SF3987.2 KD832231.4
IF4987.7 RP832231.2
EOF
不太确定如何实现这一点,然后我留下了处理后的输出,但不确定如何将它们替换回文件中的右列。
输出应该看起来非常像输入,只有那些提取的字段将由外部程序翻译:
K1353.193338DP7.7893FS4 KARE
K1353.132238DK2.7893FS4 MEAKE
K1332.132238PR7.7894FI2 LEAOS
K1329.916263DK7.7872FS3 NEDLE
K1353.132238DK3.48K3KS3 PQAKM
或者,我想知道在 GNU/Linux 环境中完成此任务的其他方法,但不使用 awk。
答案1
假设输入中的每一行(或至少以“K”开头的每一行)正好是 29 个字符长,我可以使用以下命令复制您所需的输出
转速文件名|粘贴文件名- | awk' { if (substr($0,1,1) == "K") { 打印 substr($0,1,5) substr($0,39,17) substr($0,24,7) } }'
这
rev
一次性在整个输入文件上 运行。- 显然,这会处理每一个通过外部程序在文件中添加行。您对创建管道并为每行调用一次外部程序的开销表示担忧。基于这种担忧,我认为这是一个谨慎的做法。但是,如果输入中只有几行以“K”开头,并且处理其他行的成本很高,那么可能需要更改。
rev
每行输入恰好产生一行输出。我的解决方案取决于外部程序中的行为。
paste
将输入文件与 , 的输出逐行组合(使用)rev
。对于您的示例数据,这看起来像K1353 SF3987.7PD833391.4 卡雷埃拉克 4.193338DP7.7893FS 3531K K1353 SF3987.2KD832231.4 米克埃卡姆 4.132238DK2.7893FS 3531K K1332 IF4987.7RP832231.2 LEAOS SOAEL 2.132238PR7.7894FI 2331K K1329 SF2787.7KD362619.3 针 ELDEN 3.916263DK7.7872FS 9231K K1353 SK3K84.3KD832231.3 PQAKM MKAQP 3.132238DK3.48K3KS 3531K
awk
读上面几行。每个文件都包含输入文件中的一行以及rev
该行的输出。awk
然后将每个所需的部分组合起来。
<咆哮>
你的问题有点不连贯。如果我获取您的样本输入数据,
K1353 SF3987.7PD833391.4 KARE
K1353 SF3987.2KD832231.4 MEAKE
K1332 IF4987.7RP832231.2 LEAOS
K1329 SF2787.7KD362619.3 NEDLE
K1353 SK3K84.3KD832231.3 PQAKM
并将其提供给此awk
脚本:
{
RX=substr($0,6,9)
RY=substr($0,15,9)
printf("/%s/%s/\n", RX, RY)
}
我得到这个输出:
/ SF3987.7/PD833391./
/ SF3987.2/KD832231./
/ IF4987.7/RP832231./
/ SF2787.7/KD362619./
/ SK3K84.3/KD832231./
请注意,该RX
值包括第一列和第二列之间的空格,并且该RY
值做不是包括第二列中值的最后一个字符(即第二个点之后的数字)。这确实没有意义,因为
sprintf("echo %s %s | %s", RX, RY, csmok)
语句会导致 in 中的初始空间RX
丢失。
令人困惑的是,这是与问题底部的预期结果一致,但与上面您谈论做的五段不同
echo -e "SF3987.7 PD833391.4\nSF3987.2 KD832231.4\nIF4987.7 RP832231.2" | rev
即,您在发送到的字符串中的第二个点后面包含数字rev
。
和,
您从 中提取两个不重叠(但连续)的子字符串,然后从命令中$0
拆分,所有这些都是不必要的。我可以复制你的结果output
rev
BEGIN {
csmok="rev"
}
{
type = substr($0,1,1)
if (type == "K") {
RXY=substr($0,6,18)
cmd=sprintf("echo %s | %s", RXY, csmok)
cmd | getline output
close(cmd)
sub(RXY,output)
print
}
}
即,从字符串中提取一个 18 个字符的子字符串$0
,并且不拆分该output
字符串。
请尽量使您问题中的数据合理且内部一致。
也就是说,您似乎明白,为了获得合理的答案,并不总是需要准确地发布确切问题的每一个细节。本着这种精神,请尝试在不损害其完整性的情况下使您的问题更容易理解。你的数据刺痛了我的眼睛:
- 每行的前三个字符是“K13”。这使得看到不同的角色变得更加困难。
- 在五行中的三行中,前五个字符(即整个第一列值)是“K1353”。
- 第二列中的值是 18 个字符长的字母、数字和点的无意义混乱,因此难以阅读和理解。
- 查看第二列中的值:
- 五行中有四行以“S”开头。
- 三行,以“SF”开头。
- 三行中,第三个字符是“3”。
- 四行中,第十个字符是“D”。
- 三行中,第九、第十个字符是“KD”。
- 四行中,第 11 和 12 个字符是“83”,第 16 个字符是“1”。
- 三行中,第11-16个字符是“832231”。
我建议您发布这样的示例数据:
ant 12345.hill Adam
bat 31416.cave Bruce
cat 13579.meow Felix
dog 32768.bark Angus
有了这样的输入数据,您想要的输出可能包含“tac”、“97531”、“woem”和“xileF”等字符串,人们很容易查看它们并了解它们来自哪里。与“132238DK2”不同的是,“132238DK2”需要一个人花六到八分钟用放大镜才能找到来源——几乎就像那些“单词搜索”谜题之一。 (请注意,“132238DK”不会是唯一可追踪的,因为“KD832231”出现了两次。)
</咆哮>