【问题标题】:Improving Shell Script Performance提高 Shell 脚本性能
【发布时间】:2010-11-09 00:37:10
【问题描述】:

此 shell 脚本用于从 $2 中提取一行数据,如果它包含模式 $line

$line 使用正则表达式 [A-Z0-9.-]+@[A-Z0-9.-]+(简单的电子邮件匹配)构造,形成文件 $1 中的行。

#! /bin/sh

clear

for line in `cat "$1" | grep -i -o -E "[A-Z0-9.-]+@[A-Z0-9.-]+"`
do
    echo `cat "$2" | grep -m 1 "\b$line\b"`
done

文件 $1 包含短数据行(

文件 $2 的文本行稍长(> 80 到

运行此功能的桌面具有大量 RAM (6 Gig) 和 2-4 核的 Xenon 处理器。

是否有任何快速修复来提高性能,因为目前完全运行(并输出到另一个文件)需要 1-2 小时。

注意:我对所有建议持开放态度,但我们无法复杂地重写整个系统等。此外,数据来自第三方并且容易出现随机格式。

【问题讨论】:

  • 电子邮件地址模式并不是完全正确的。正如有人已经指出的那样,它缺乏对下划线的支持,这很常见。它还缺乏对许多其他字符的支持(缺乏对加号的支持,因为我经常在地址中使用加号,这是我最喜欢的小毛病)。请参阅en.wikipedia.org/wiki/E-mail_address#Validation 了解更多信息,以及ex-parrot.com/~pdw/Mail-RFC822-Address.html 了解更完整的电子邮件地址正则表达式。
  • 感谢 Mikael,+1 提供信息

标签: regex performance unix shell grep


【解决方案1】:

快速建议:

  1. 避免使用useless use of cat 并将cat X | grep Y 更改为grep Y X

  2. 您可以处理 grep 输出,因为它是通过管道而不是使用反引号产生的。使用反引号需要先完成第一个 grep,然后才能开始第二个 grep

因此:

grep -i -o -E "[A-Z0-9.-]+@[A-Z0-9.-]+" "$1" | while read line; do
    grep -m 1 "\b$line\b" "$2"
done

下一步:

  1. 不要重复处理$2。很大。您可以保存所有模式,然后对文件执行一次 grep。
  2. 将循环替换为sed

不再重复grep

grep -i -o -E "[A-Z0-9.-]+@[A-Z0-9.-]+" "$1" | sed -E 's/^|$/\\1/g' > patterns
grep -f patterns "$2"

最后,使用一些 bash 幻想(请参阅 man bash → 进程替换),我们可以放弃临时文件并在一行中执行此操作:

grep -f <(grep -i -o -E "[A-Z0-9.-]+@[A-Z0-9.-]+" "$1" | sed -E 's/^|$/\\b/g') "$2"

这很好,除非你有这么多模式grep -f 用完内存和 barfs。如果发生这种情况,您需要分批运行它。烦人,但可行:

grep -i -o -E "[A-Z0-9.-]+@[A-Z0-9.-]+" "$1" | sed -E 's/^|$/\\1/g' > patterns

while [ -s patterns ]; do
    grep -f <(head -n 100 patterns) "$2"
    sed -e '1,100d' -i patterns
done

这将一次处理 100 个模式。它一次可以做的越多,越少通过你的第二个文件。

【讨论】:

  • 一点点性能提升,就是完全省略了while read循环的使用。这是因为如果说找到 1000 个电子邮件地址,脚本将在 file2 上调用 grep 1000 次。 grep -i -o -E "[A-Z0-9.-]+@[A-Z0-9.-]+" file1 > temp grep -f temp file2
  • 使用你的最终解决方案 grep 给出了 out of memory 错误。
【解决方案2】:

问题是你传递了太多的 shell 命令,以及不必要的 cat 使用。

仅使用 awk 的一种可能解决方案

awk 'FNR==NR{
    # get all email address from file1
    for(i=1;i<=NF;i++){
        if ( $i ~ /[a-zA-Z0-9.-]+@[a-zA-Z0-9.-]+/){
            email[$i]
        }
    }
    next
}
{
 for(i in email) {
    if ($0 ~ i) {
        print 
    }
 }
}' file1 file2

【讨论】:

    【解决方案3】:

    我会取消循环,因为 greping 200 万行文件 50k 次可能非常昂贵;)

    为了让你把循环拿出来 首先使用外部 grep 命令创建一个包含所有电子邮件地址的文件。 然后将其用作模式文件,通过使用 grep -f

    执行辅助 grep

    【讨论】:

      【解决方案4】:

      如果 $1 是一个文件,不要使用“cat | grep”。相反,将文件直接传递给 grep。应该是这样的

      grep -i -o -E "[A-Z0-9.-]+@[A-Z0-9.-]+" $1
      

      此外,您可能需要调整正则表达式。您至少应该在电子邮件地址中看到下划线(“_”),所以

      grep -i -o -E "[A-Z0-9._-]+@[A-Z0-9.-]+" $1
      

      【讨论】:

      • 不要以为域名中可以有_。所以 /[A-Z0-9._-]+@[A-Z0-9.-]+/ 最好
      【解决方案5】:

      正如 John Kugelman 已经回答的那样,通过管道而不是使用反引号来处理 grep 输出。如果您使用反引号,则将首先运行反引号内的整个表达式,然后将反引号的输出作为参数运行外部表达式。

      首先,这将比必要的慢很多,因为管道将允许两个程序同时运行(如果它们都是 CPU 密集型并且您有多个 CPU,这真的很好)。然而,还有另一个非常重要的方面,行

      for line in `cat "$1" | grep -i -o -E "[A-Z0-9.-]+@[A-Z0-9.-]+"`
      

      可能会变得太长,以至于 shell 无法处理。大多数 shell(至少据我所知)限制了命令行的长度,或者至少限制了命令的参数,我认为这也可能成为 for 循环的问题。

      【讨论】:

        猜你喜欢
        • 2013-10-09
        • 1970-01-01
        • 1970-01-01
        • 2020-06-05
        • 2022-01-09
        • 1970-01-01
        • 1970-01-01
        • 2012-10-16
        • 2013-01-16
        相关资源
        最近更新 更多