【问题标题】:Print lines in one file matching patterns in another file在一个文件中打印与另一个文件中的模式匹配的行
【发布时间】:2021-01-15 21:58:29
【问题描述】:

我有一个超过 40.000 行的文件(file1),我想提取与 file2 中的模式匹配的行(大约 6000 行)。我这样用grep,但是很慢:

grep -f file2 file1 > out

使用awksed 是否有更快的方法来执行此操作?

以下是我文件中的一些摘录:

File1:

scitn003869.2| scign003869 CGCATGTGTGCATGTATTATCGTATCCCTTG
scitn007747.1| scign007747  CACGCAGACGCAGTGGAGCATTCCAGGTCACAA
scitn003155.1| scign003155  TAAAAATCGTTAGCACTCGCTTGGTACACTAAC
scitn018252.1| scign018252  CGTGTGTGTGCATATGTGTGCATGCGTG
scitn004671.2| scign004671  TCCTCAGGTTTTGAAAGGCAGGGTAAGTGCT

File2:

scign000003
scign000004
scign000005
scign004671
scign000013

【问题讨论】:

    标签: unix sed awk grep extract


    【解决方案1】:

    试试grep -Fwf file2 file1 > out

    -F 选项指定纯字符串匹配,因此应该更快,而无需使用正则表达式引擎。

    【讨论】:

    • 这项工作只花了大约一秒钟!
    • Fgrep 是这个原生选项等价的,可能还快几毫秒
    • 我的 grep 手册页说:“fgrep 与 grep -F 相同。直接调用 egrep 或 fgrep 已被弃用,...”
    【解决方案2】:

    这是在 awk 中的操作方法:

    awk 'NR==FNR{pats[$0]; next} $2 in pats' File2 File1
    

    使用 60,000 行的 File1(您的 File1 重复 8000 次)和 6,000 行的 File2(您的重复 1200 次):

    $ time grep -Fwf File2 File1 > ou2
    
    real    0m0.094s
    user    0m0.031s
    sys     0m0.062s
    
    $ time awk 'NR==FNR{pats[$0]; next} $2 in pats' File2 File1 > ou1
    
    real    0m0.094s
    user    0m0.015s
    sys     0m0.077s
    
    $ diff ou1 ou2
    

    即它和 grep 一样快。不过需要注意的一点是,awk 解决方案允许您选择要匹配的特定字段,因此如果 File2 中的任何内容出现在 File1 中的其他任何位置,您将不会得到错误匹配。它还允许您一次匹配整个字段,因此如果您的目标字符串长度不同,并且您不希望“scign000003”匹配“scign0000031”(尽管 grep 的 -w 对此提供了类似的保护)。

    为了完整起见,这里是发布 elsethread 的其他 awk 解决方案的时间:

    $ time awk 'BEGIN{i=0}FNR==NR{a[i++]=$1;next}{for(j=0;j<i;j++)if(index($0,a[j]))print $0}' File2 File1 > ou3
    
    real    3m34.110s
    user    3m30.850s
    sys     0m1.263s
    

    这是我发布的 perl 脚本 Mark 发布的时间:

    $ time ./go.pl > out2
    
    real    0m0.203s
    user    0m0.124s
    sys     0m0.062s
    

    【讨论】:

    • 虽然这可能符合 OP 的意思和要求,我确实看到你提到了这一点并将其描述为避免错误匹配的好处,但 OP 应该意识到它实际上并没有这样做和他的代码一样。他的代码会在该行的任意位置找到匹配项,而您的代码只会在第二个字段中找到匹配项。
    【解决方案3】:

    你可以试试这个 awk:

    awk 'BEGIN{i=0}
    FNR==NR { a[i++]=$1; next }
    { for(j=0;j<i;j++)
        if(index($0,a[j]))
            {print $0;break}
    }' file2 file1
    

    FNR==NR 部分指定花括号中的内容仅在处理第一个输入文件 (file2) 时应用。它说将您要查找的所有单词保存在数组a[] 中。第二组花括号中的位适用于第二个文件的处理...在读入每一行时,将其与a[] 的所有元素进行比较,如果找到,则打印该行。就是这样!

    【讨论】:

    • 完美运行。而且比我的 grep 命令快得多。谢谢!
    • 您将通过if (index($0, a[j]) {print; break}获得一些效率
    • 好主意,格伦。谢谢。
    • 如果你对文件进行排序,仍然可以优化(特别是如果一个在运行时几乎每次都相同,我猜是 file2)
    【解决方案4】:

    只是为了好玩,这里有一个 Perl 版本:

    #!/usr/bin/perl
    use strict;
    use warnings;
    my %patterns;
    my $srch;
    
    # Open file and get patterns to search for
    open(my $fh2,"<","file2")|| die "ERROR: Could not open file2";
    while (<$fh2>)
    {
       chop;
       $patterns{$_}=1;
    }
    
    # Now read data file
    open(my $fh1,"<","file1")|| die "ERROR: Could not open file1";
    while (<$fh1>)
    {
       (undef,$srch,undef)=split;
       print $_ if defined $patterns{$srch};
    }
    

    这里是一些时序,每个 Ed 的文件创建方法使用 60,000 行文件 1 和 6,000 行文件 2:

    time awk 'NR==FNR{pats[$0]; next} $2 in pats' file2 file1 > out
    real    0m0.202s
    user    0m0.197s
    sys     0m0.005s
    
    time ./go.pl > out2
    real    0m0.083s
    user    0m0.079s
    sys     0m0.004s
    

    【讨论】:

    • +1 不错。想知道为什么这比awk 解决方案快两倍以上。顺便说一下,我假设您也将 perl 程序的输出保存到文件中? (在你回答./go.pl之后没有重定向)
    • 这是我“最好的”Perl,我确实利用了我所知道的关于数据的一切,比如使用 Chop() 然后 (undef, $srch,undef) 丢弃我不需要的东西awk 没有那么奢侈。我确实将它保存到了一个文件中,但只是复制并粘贴了时间,而不必编辑我的提示。
    • 我得到的测量结果与上述完全相反(参见我更新的帖子),perl 的速度是 awk 或 grep 的两倍。我怀疑 Mark 在计时之前没有运行脚本几次来考虑缓存。
    • 多么奇怪!我确实运行了它们几次,它们在我的 Mac 上非常一致 - 它使用 SSD 运行,这通常使时序保持一致 - 没有旋转延迟或寻道时间。
    • 身份证。我发现 perl 至少可以说有点难以阅读,但它看起来就像我的 awk 脚本所做的一样,只是手写循环并将行拆分为字段,所以我希望 perl 可能由于必须手动编写循环/拆分代码而不是使用内置功能,所以速度会慢一些,但我希望这两个脚本大约在同一个球场上。我想当您谈论的是 0.1 秒和 0.2 秒时,这大约是在同一个球场上,而其他一些解决方案大约在几分钟内。
    【解决方案5】:

    只是为了学习:我正在解决同样的问题,我想出了各种解决方案(包括read $line 循环等)。当我到达上面找到的grep one-liner 时,我仍然得到了错误的输出。然后我意识到我的 PATTERN 文件有 2 行尾随...所以grep 从我的数据库中提取了我所有的行。道德:检查尾随空格/行。此外,在具有数百种模式的更大数据集上运行该命令,time 甚至无法计数。

    【讨论】:

    • 啊,非常感谢!我想知道为什么看起来我的整个文件都返回了
    • grep -Fwf &lt;(grep '[^[:blank]]' file2) file1 将只包含包含非空白字符的行。
    • 我会补充一点,如果你有一个“黑名单”并且你想要文件2中没有的所有内容,那么你只需像这样添加-vgrep -vFwf file2 file1 &gt; out