【问题标题】:awk multiple passes on each lineawk 在每一行上多次传递
【发布时间】:2014-11-04 23:56:48
【问题描述】:

我必须处理一些大文件并在每一行上运行多个测试。我目前正在使用 awk 运行单个测试,并使用“while-read-line-do”循环将每一行传递给十几个 so awk 命令,这些命令测试各个列以验证其内容并记录错误。通过所有测试的行将附加到 .VALID 文件中。

我目前面临的问题是这个过程非常缓慢。在阅读了网络上和 StackOverflow 上的许多其他帖子后,我收集到的信息是,主要的罪魁祸首是“while-read-line-do”循环,它不会将文件(每个大约 100K 行)借入内存。

我希望这里有人可以帮助我找出更好的实现方法,以便我可以获得类似 awk 的性能。这是我的代码的简化版本:

while read line || [[ -n "$line" ]];do
    echo $line | awk -F\; '{
        if ( ($3 != "P") && ($3 != "0") ) {print $0 "; ERROR ;" >> "INPUT_FILE.ERRORS"};
            else print $0 >> "INPUT_FILE.OK";
        }'
    echo $line | awk -F\; '{
        if ( ($7 < 10) || ($7 > 3) ) {print $0 "; ERROR ;" >> "INPUT_FILE.ERRORS"};
            else print $0 >> "INPUT_FILE.OK";
        }'  
    echo $line | awk -F\; '{
        if ( ($36 < 0) || ($36 > 1000) ) {print $0 "; ERROR ;" >> "INPUT_FILE.ERRORS"};
            else print $0 >> "INPUT_FILE.OK";
        }'
done < INPUT_FILE.txt

理想情况下,我正在尝试提出一种解决方案,允许我使用基于 awk 的循环在每行中进行多次传递。

提前致谢。

【问题讨论】:

  • 只需将所有 awk 块/测试放入一个脚本中,不要使用 next 或任何内容来跳过该行。 awk 针对每一行运行所有块。您根本不需要多次运行 awk 或读取循环。
  • 嗨@EtanReisner。感谢您的快速回复。我不太确定如何使用 awk 遍历行,而不是“while read line do”。你能不能放过我一点,分享一个简单的例子。再次感谢!
  • if ( ($7 &lt; 10) || ($7 &gt; 3) ) 在这种情况下似乎存在错误,因为它始终为真。应该是&amp;&amp; 而不是||
  • 你不会在 awk 中循环。 awk 为您循环。 awk 在每一行上自动运行你给它的整个脚本。只需给它文件和所有块/模式/if语句/等。
  • @TimZimmermann 我构建了一个简单的例子,因为我的条件有点复杂。

标签: bash awk


【解决方案1】:

绝对没有必要将这些行一一传递给 awk; awk 代表您逐行处理文件。您答案中的代码可以简化为:

awk -F\; '($3!="P"&&$3!="0")||($7<10||$7>3)||($36<0||$36>1000)
          {print $0 "; ERROR ;" >> "INPUT_FILE.ERRORS"; next}
          {print >> "INPUT_FILE.OK"}' INPUT_FILE.txt

我怀疑这会快很多。

awk 程序的结构是condition { action },所以很少需要使用if/else。相反,您可以在 if 分支中使用 next,这意味着 awk 将跳到下一行而不是运行第二个块。

此输出将略有不同,因为在错误日志中不会重复出现多个测试失败的行。我认为这没问题,因为无论如何您的每项检查的输出都是相同的。

为了进一步提高性能,您可以考虑按可能性顺序排列测试,因为这意味着该条件更有可能发生短路。

请注意,在 awk 中,&gt;&gt;&gt; 与 shell 中的含义不同。 &gt; 将意味着 awk 第一次创建一个新文件并在连续写入时附加到它,因此您可能想要使用它。如果该文件尚不存在,那也没关系。

正如 cmets 中所述,$7&lt;10||$7&gt;3 似乎存在逻辑错误,因为这始终是正确的。也许你把&gt;&lt; 搞混了?

如果您想为每个错误编写单独的输出,您可以将结构稍微更改为如下所示:

awk -F\; '{f=0}
          $3!="P"&&$3!="0" {print $0 "; ERROR ;" >> "INPUT_FILE.ERRORS"; f=1}
          $7<3||$7>10      {print $0 "; ERROR ;" >> "INPUT_FILE.ERRORS"; f=1}
          $36<0||$36>1000  {print $0 "; ERROR ;" >> "INPUT_FILE.ERRORS"; f=1}
          !f {print >> "INPUT_FILE.OK"}' INPUT_FILE.txt

每个测试都是单独进行的,如果任何测试为真,f 将设置为真。如果在线上的所有测试后f 仍然为假,则将其打印到 OK 文件中。我还改变了你的第二个测试,所以它并不总是正确的。

【讨论】:

  • 感谢您的回复。虽然这肯定更快,但不幸的是这对我来说不起作用。我需要在每一行上执行每个测试,并记录相应的错误。因此,如果一行不满足 12 个条件中的 3 个,则将记录所有三个错误。最后,如果任何测试未通过,则该行不应进入 .VALID 文件。
  • 我已经更新了我的答案以提供一个建议,为每个测试打印单独的行。
  • 谢谢一百万。这正是我所需要的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多