提高在 awk 中使用“系统”调用(shell 转义)处理大文件时的性能

提高在 awk 中使用“系统”调用(shell 转义)处理大文件时的性能

我有一个 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拆分,所有这些都是不必要的。我可以复制你的结果outputrev

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”出现了两次。)

</咆哮>

相关内容