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

时间:2012-09-21 19:22:08

标签: linux bash unix terminal grep

我有两个包含大量文本的大文件,而我要做的就是将文件A中的所有行保留在与文件B中的字段匹配的字段中。

文件A类似于:

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

文件B我设法使用cut和sed等基本上将它归结为一个列表的字段。

所以目标是将文件A中的所有行保留在第4个字段(它表示KEYFIELD)中,如果该行的字段与文件B中的某行匹配。(不必是精确匹配,所以如果文件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

4 个答案:

答案 0 :(得分:3)

您正在达到使用基本shell工具的极限。假设每行约40个字符,文件A中有400,000行,文件B中有大约1,200,000行。你基本上是为文件A中的每一行运行grep,并且每次执行都要使用grep犁通过1,200,000行。这是您正在解析的480 BILLION 行。 Unix工具的速度惊人,但即使快速完成了8000亿次,也会加起来。

使用像Perl或Python这样的完整编程脚本语言会更好。您将文件B 中的所有行都放在哈希中。您在文件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;

哈希意味着你只需要通读file_b一次而不是400,000次。启动程序,从办公室厨房拿一杯咖啡。 (百胜!非乳制品奶精!)当你回到办公桌时,它就会完成。

答案 1 :(得分:2)

这是使用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还可以使用fileB.txtcut执行您所描述的sed的预处理。如果您希望我将其构建到上面的脚本中,您需要提供此行的示例。


使用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
    }
}

这可以在GenBasicV12中找到可以在HumanGenCodeV12中找到的行。输出文件(output.txt)包含65340行。该脚本只需不到10秒即可完成。

答案 2 :(得分:0)

即使对于中等大小的模式文件(<1MB),

grep -f似乎也很慢。我猜它会尝试输入流中每一行的每个模式。

对我而言更快的解决方案是使用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模式匹配。

答案 3 :(得分:0)

使用以下命令:

awk 'FNR==NR{a[$0];next}($4 in a)' <your filtered fileB with single field> fileA