Bash:如何保留一个文件中与另一个文件中行匹配的字段?

4

我有两个大文件,里面有很多文本。我的任务是保留文件A中所有包含文件B中某个字段的行。

文件A类似于:

Name (tab)  #  (tab)  #  (tab)  KEYFIELD  (tab)  Other fields

我成功使用了cut、sed和其他工具,将文件B中的内容处理成了一个列表。

因此,目标是如果文件A中的第四个字段(称为KEYFIELD)与文件B中的某行匹配,则保留文件A中的所有行。(不必完全匹配,所以如果文件B中有“Blah”,而文件A中有“Blah_blah”,也可以匹配成功)

我尝试过:

grep -f fileBcutdown fileA > outputfile

编辑:好吧,我放弃了。我刚刚强制结束了它。

有更好的方法吗?对于任何关心的人,文件A为13.7MB,削减后的文件B为32.6MB。

编辑:这是文件A中的一行示例:

chr21 33025905 33031813 ENST00000449339.1 0 - 33031813 33031813 0 3 1835,294,104, 0,4341,5804,

来自文件B的示例行被削减:

ENST00000111111

在你的第一句话中,你说文件大小超过400 MB,但在最后一句话中,它们只有约50 MB。45分钟后outputfile的内容是什么?顺便说一句,CPU不会成为瓶颈,但慢/快硬盘可能会产生很大的影响。 - knittl
啊,我应该说得更清楚一些。在裁剪之前,文件B的大小超过了400MB。在我扔掉所有不需要的东西后,它变成了32.6MB。这只是一个7200转/分钟的硬盘,但当我使用"less outputfile"命令时,输出文件却是空的。糟糕。我已经编辑了原帖以使其更清晰明了。 - Joe
4个回答

3
您正在使用基本的shell工具,已经达到了使用限制。假设每行约40个字符,File A有400,000行,File B有大约1,200,000行。您基本上是在为File A中的每一行运行grep,并让grep在每次执行时穿过1,200,000行。这是您要解析的480亿 行。Unix工具非常快,但即使是快速完成的操作,也会在4800亿次操作后变得缓慢。
最好使用完整的编程脚本语言,如Perl或Python。您将所有行放入File B中,然后检查File A中的每一行,以查看第四个字段是否与哈希表中的某些内容匹配。
读取几十万行?创建10,000,000个条目的哈希表?Perl可以在几分钟内解析它们。
顶多是参考一下,因为您没有给出太多相关信息,所以我没有进行任何测试:
#! /usr/bin/env perl

use strict;
use warnings;
use autodie;
use feature qw(say);

# Create your index
open my $file_b, "<", "file_b.txt";
my %index;

while (my $line = <$file_b>) {
    chomp $line;
    $index{$line} = $line;    #Or however you do it...
}
close $file_b;


#
# Now check against file_a.txt
#

open my $file_a, "<", "file_a.txt";
while (my $line = <$file_a>) {
    chomp $line;
    my @fields = split /\s+/, $line;
    if (exists $index{$field[3]}) {
         say "Line: $line";
    }
}
close $file_a;

哈希意味着您只需要读取文件B一次,而不是400,000次。启动程序后,去办公室厨房拿杯咖啡(好喝!非乳制奶精!)。当您回到桌子前,程序就已经完成了。


我之前并没有真正使用过Perl或Python,所以...是的。尽管如此,我会尝试运行你的脚本。感谢你的帮助!我编辑了原始帖子,所以现在包括两个文件的外观。 - Joe
等一下。我刚刚注意到这个东西实际上没输出任何内容。如果我想在你的脚本中加入从文件A传递的输出内容,我需要添加什么呢?对不起,我完全不懂Perl。 - Joe
这并不满足原帖作者的要求,即匹配“不必是完全匹配,因此如果文件B有Blah而文件A说Blah_blah,那也可以”。 - ruakh
@DavidW.:“我想‘不精确’的要求更多是由于使用grep而不是最初的要求。”:啊,你可能是对的。希望原帖作者能澄清这一点。 - ruakh
啊,抱歉让你感到困惑。当我说不需要完全匹配时,我的意思是确实如此。因此,如果文件B有一行叫做Blah,并且文件A的某一行有Blah_blah,那么文件A中的那一行必须被包括在内。 - Joe
显示剩余5条评论

3

以下是使用GNU awk的一种方法。执行如下:

awk -f script.awk fileB.txt fileA.txt

script.awk的内容:

FNR==NR {
    array[$0]++
    next
}

{
    line = $4
    sub(/\.[0-9]+$/, "", line)
    if (line in array) {
        print
    }
}

或者,这里有一行代码:

awk 'FNR==NR { array[$0]++; next } { line = $4; sub(/\.[0-9]+$/, "", line); if (line in array) print }' fileB.txt fileA.txt

GNU awk也可以执行你用cutsed描述的fileB.txt的预处理。如果你想让我将其构建到上面的脚本中,你需要提供这行代码的示例。


使用文件HumanGenCodeV12GenBasicV12进行更新:

运行方式如下:

awk -f script.awk HumanGenCodeV12 GenBasicV12 > output.txt

script.awk的内容如下:

FNR==NR {
    gsub(/[^[:alnum:]]/,"",$12)
    array[$12]++
    next
}

{
    line = $4
    sub(/\.[0-9]+$/, "", line)
    if (line in array) {
        print
    }
}

这成功地打印了HumanGenCodeV12中可以找到的GenBasicV12行。输出文件(output.txt)包含65340行,脚本不到10秒即可完成。


你好,感谢回答。文件B已经被处理成另一个文件了,所以我不需要它。我现在会尝试运行你的脚本,看看会发生什么。 - Joe
嗯,它确实起作用了……有点。这给了我一个2.1MB的输出文件,而我找到并编写的另一个脚本给了我一个11MB的输出文件(!)。我仍在努力弄清楚其中的区别。 - Joe
嗯,看起来这个脚本在进行到1/5到1/4的时候就停止了,而没有继续执行完整个过程。这很奇怪。编辑:这是针对单行脚本的情况。另一个脚本好像什么都没有返回。嗯。 - Joe
@Joe:听起来你的数据有一些不一致。脚本和上面的一行代码应该输出完全相同的结果。你能否给我发送更多的示例数据或者完整的文件(最好是在任何处理之前)?我建议使用dropboxmediafire这样的工具。 - Steve
@Joe:正如你在问题中描述的那样,FileB.txt必须是此脚本参数列表中的第一个,才能正确运行。你可能不小心混淆了文件名?另外,如果使用某种压缩方式,400 MB的文件将变成大约100 MB。这非常适合发送。运行命令:tar cjf files.tar.bz2 *.txt - Steve
显示剩余4条评论

1
请使用以下命令:
awk 'FNR==NR{a[$0];next}($4 in a)' <your filtered fileB with single field> fileA

1

grep -f 似乎对于中等大小的模式文件(<1MB)非常慢。我猜测它会尝试每个模式与输入流中的每一行匹配。

一个更快的解决方案是使用 while 循环。这假设 fileA 是相当小的(在您的示例中是较小的那个),因此多次迭代较小的文件比多次迭代较大的文件更可取。

while read line; do
  grep -F "$line" fileA
done < fileBcutdown > outputfile

请注意,如果循环匹配多个模式,则此循环将多次输出一行。要解决此限制,请使用sort -u,但这可能会慢得多。你必须尝试。
while read line; do
  grep -F "$line" fileA
done < fileBcutdown | sort -u | outputfile

如果您依赖于行的顺序,那么我认为您没有其他选择,只能使用grep -f。但基本上归结为尝试m*n个模式匹配。


哦...算了吧。就是我在最后一句话中写的,你需要运行m*n个匹配操作。也许最好一次性将所有模式加载到内存中,然后迭代文件A,在找到第一个匹配项时跳过所有剩余的模式(使用比shell脚本更好的编程语言)。 - knittl
好的,感谢你的帮助。在缩小 FileB 的大小后,我使用了 sort 进行排序。虽然 Grep -f 仍在运行,但输出文件仍为空。唉,我马上会尝试你的解决方案。 - Joe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接