【问题标题】:grep -f maximum number of patterns?grep -f 最大模式数?
【发布时间】:2013-01-20 19:12:21
【问题描述】:

我想在带有 -f 的文本文件上使用 grep 来匹配一长串(10,000 个)模式。原来 grep 不喜欢这个(谁知道?)。一天后,它没有产生任何东西。较小的列表几乎可以立即生效。

我在想我可以把我的长长的清单分开做几次。知道模式列表的最大长度可能是多少吗?

另外,我对 unix 还比较陌生。欢迎替代方法。模式列表或搜索词在纯文本文件中,每行一个。

感谢大家的指导。

【问题讨论】:

  • 您是否正在寻找包含“任何这 10k 个术语”的行?
  • 我认为在大小为 m 的文件中对 n 个模式进行 grepping 应该是相当明显的 O(nm)。除非您可以将您的模式合并为更少的模式(通过注意某些模式中的共同部分并在正则表达式中明智地使用|),否则我看不出如果不预先构建某种索引,您如何才能真正改进这一点。如果您不这样做而只是使用幼稚的grep,那么,与往常一样,您需要通过测试和基准测试来确定在您的用例中*可以接受的 n 和 m 的最大大小。
  • 即时工作的列表要小多少?你能从模式文件中显示几行吗?
  • 我刚刚尝试了 10 个列表。只是用行分隔的 Twitter 用户名。刚试了1000的列表,10分钟后没有输出。
  • 我刚刚尝试了类似的事情 - 对照 30k 行的文件检查 272 个名称的列表。这可能比您的问题要小得多;在旧 Mac 上只用了 0.367 秒。使用time grep -f file1 file2 会告诉您需要多长时间。用 50、100、200 个名字运行它,看看会发生什么。可能在某个时间点您遇到内存(交换)问题,之后时间会爆炸。如果进程运行时间较长,请打开另一个窗口并使用top 找出使用了多少内存;或使用free 命令。通常当事情“挂起”时,内存不足是罪魁祸首。

标签: unix grep


【解决方案1】:

从 cmets 看来,您匹配的模式是固定字符串。如果是这种情况,您绝对应该使用-F。这将大大提高匹配的速度。 (在中等功率的机器上使用 479,000 个字符串匹配 3 行使用 -F 的输入文件需要不到 1.5 秒。不使用 -F,同一台机器在几分钟后还没有完成。)

【讨论】:

  • 多么简单的解决方案啊。奇迹般有效。非常感谢。
  • 占用大量内存/RAM。
【解决方案2】:

我遇到了同样的问题。在一个包含 900 万行的文件中搜索 400 万个模式。好像是RAM的问题。所以我得到了这个整洁的小工作,它可能比拆分和加入要慢,但它只需要这一行。

 while read line; do grep $line fileToSearchIn;done < patternFile

我需要使用解决方法,因为 -F 标志对于大文件没有解决方案...

编辑:对于大文件来说,这似乎真的很慢。经过更多研究后,我发现了来自 Kent NGS-editing-Tools

的“faSomeRecords”和其他很棒的工具

我自己尝试从 550 万条记录文件中提取 200 万条 fasta-rec。花了大约。 30 秒..

干杯

编辑:direct download link

【讨论】:

    【解决方案3】:

    这是一个 bash 脚本,您可以在文件(或者如果您愿意,也可以是文件的子集)上运行。它将密钥文件分割成越来越大的块,并为每个块尝试 grep 操作。这些操作是计时的——现在我正在计时每个 grep 操作,以及处理所有子表达式的总时间。 输出以秒为单位 - 通过一些努力,您可以获得毫秒,但您遇到的问题不太可能需要这种粒度。 使用以下形式的命令在终端窗口中运行脚本

    ./timeScript keyFile textFile 100 &gt; outputFile

    这将运行脚本,使用 keyFile 作为存储搜索键的文件,使用 textFile 作为您要查找键的文件,并使用 100 作为初始块大小。在每个循环中,块大小都会加倍。

    在第二个终端中,运行命令

    tail -f outputFile

    它将跟踪您的其他进程的输出到文件outputFile

    我建议您打开第三个终端窗口,并在该窗口中运行top。您将能够看到您的进程占用了多少内存和 CPU - 同样,如果您看到大量内存消耗,它会提示您事情进展不顺利。

    这应该可以让您了解事情何时开始放缓 - 这就是您问题的答案。我不认为有一个“神奇的数字”——它可能取决于您的机器,尤其是文件大小和您拥有的内存量。

    您可以获取脚本的输出并通过 grep:

    grep entire outputFile

    您最终只会得到摘要 - 块大小和所用时间,例如

    Time for processing entire file with blocksize 800: 4 seconds

    如果您将这些数字相互对照(或简单地检查数字),您将看到算法何时最佳,何时变慢。

    这是代码:我没有进行广泛的错误检查,但它似乎对我有用。显然,在您的最终解决方案中,您需要对 grep 的输出做一些事情(而不是将其通过管道传送到 wc -l,我这样做只是为了查看匹配了多少行)...

    #!/bin/bash
    # script to look at difference in timing
    # when grepping a file with a large number of expressions
    # assume first argument = name of file with list of expressions
    # second argument = name of file to check
    # optional third argument = initial block size (default 100)
    #
    # split f1 into chunks of 1, 2, 4, 8... expressions at a time
    # and print out how long it took to process all the lines in f2
    
    if (($# < 2 )); then
      echo Warning: need at leasttwo parameters.
      echo Usage: timeScript keyFile searchFile [initial blocksize]
      exit 0
    fi
    
    f1_linecount=`cat $1 | wc -l`
    echo linecount of file1 is $f1_linecount
    
    f2_linecount=`cat $2 | wc -l`
    echo linecount of file2 is $f2_linecount
    echo
    
    if (($# < 3 )); then
      blockLength=100
    else
      blockLength=$3
    fi
    
    while (($blockLength < f1_linecount))
    do
      echo Using blocks of $blockLength
      #split is a built in command that splits the file
      # -l tells it to break after $blockLength lines
      # and the block$blockLength parameter is a prefix for the file
      split -l $blockLength $1 block$blockLength
      Tstart="$(date +%s)"
      Tbefore=$Tstart
    
      for fn in block*
        do
          echo "grep -f $fn $2 | wc -l"
          echo number of lines matched: `grep -f $fn $2 | wc -l`
          Tnow="$(($(date +%s)))"
          echo Time taken: $(($Tnow - $Tbefore)) s
          Tbefore=$Tnow
        done
      echo Time for processing entire file with blocksize $blockLength: $(($Tnow - $Tstart)) seconds
      blockLength=$((2*$blockLength))
      # remove the split files - no longer needed
      rm block*
      echo block length is now $blockLength and f1 linecount is $f1_linecount
    done
    
    exit 0
    

    【讨论】:

    • 哇。非常感谢你做的这些。致未来的读者:这确实会优化每个 grep 的时间。对于我的特殊情况,这不是必需的,因为模式是固定的字符串,并且 -F 以指数方式加速了该过程。
    【解决方案4】:

    您当然可以尝试 sed 看看您是否获得了更好的结果,但是在任何大小的文件上都需要做很多工作。您没有提供有关您的问题的任何详细信息,但是如果您有 10k 模式,我会尝试考虑是否有某种方法可以将它们概括为较少数量的正则表达式。

    【讨论】:

      【解决方案5】:

      这是一个 perl 脚本“match_many.pl”,它解决了“大量键与大量记录”问题的一个非常常见的子集。从标准输入每行接受一个键。两个命令行参数是要搜索的文件的名称和必须匹配键的字段(空格分隔)。原始问题的这个子集可以快速解决,因为记录中匹配项(如果有)的位置是提前知道的,并且键始终对应于记录中的整个字段。在一个典型的案例中,它用 42899 个键搜索了 9400265 条记录,匹配​​了 42401 个键,并在 41 秒内发出了 1831944 条记录。更一般的情况,其中键可能作为子字符串出现在记录的任何部分,这是该脚本未解决的更困难的问题。 (如果键从不包含空格并且始终对应于整个单词,则可以修改脚本以通过迭代每条记录的所有字段来处理这种情况,而不是只测试一个,代价是运行速度慢 M 倍,其中 M 是找到匹配项的平均字段数。)

      #!/usr/bin/perl -w
      use strict;
      use warnings;
      my $kcount;
      my ($infile,$test_field) = @ARGV;
      if(!defined($infile) || "$infile" eq "" || !defined($test_field) || ($test_field <= 0)){
        die "syntax: match_many.pl infile field" 
      }
      my %keys;       # hash of keys
      $test_field--;  # external range (1,N) to internal range (0,N-1)
      
      $kcount=0;
      while(<STDIN>) {
         my $line = $_;
         chomp($line);
         $keys {$line} = 1;
         $kcount++
      }
      print STDERR "keys read: $kcount\n";
      
      my $records = 0;
      my $emitted = 0;
      open(INFILE, $infile )  or die "Could not open $infile";
      while(<INFILE>) {
         if(substr($_,0,1) =~ /#/){ #skip comment lines
           next;
         }
         my $line = $_;
         chomp($line);
         $line =~ s/^\s+//;
         my @fields = split(/\s+/, $line);
         if(exists($keys{$fields[$test_field]})){
            print STDOUT "$line\n";
            $emitted++;
            $keys{$fields[$test_field]}++;
         }
         $records++;
      }
      
      $kcount=0;
      while( my( $key, $value ) = each %keys ){
         if($value > 1){ 
            $kcount++; 
         }
      }
      
      close(INFILE);
      print STDERR "records read: $records, emitted: $emitted; keys matched: $kcount\n";
      
      exit;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-17
        • 2019-11-04
        相关资源
        最近更新 更多