【问题标题】:Bash: How to keep lines in a file that have fields that match lines in another file?Bash:如何在文件中保留与另一个文件中的行匹配的字段?
【发布时间】:2012-09-14 06:05:36
【问题描述】:

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

文件 A 类似于:

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

文件 B 我设法使用 cut 和 sed 以及其他东西基本上将其归结为一个列表字段。

因此,如果该行的字段与文件 B 中的行之一匹配,则目标是将文件 A 中的所有行保留在第 4 个字段中(它显示为 KEYFIELD)。(不必完全匹配,所以如果文件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 不会是这里的瓶颈,但慢速/快速硬盘驱动器会产生很大的不同。
  • 啊,我应该更清楚。削减之前的文件 B 是 400MB+。在我扔掉所有我不需要的东西之后,它是 32.6MB。这只是一个 7200rpm 的驱动器,当我少做输出文件时,输出文件是……空的。废话。编辑了原始帖子以使其更清晰。

标签: linux bash unix terminal grep


【解决方案1】:

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 模式匹配。

【讨论】:

  • 嗯……算了。这就是我在上一句中写的,你需要运行 m*n 匹配操作。也许您最好一次将模式全部加载到内存中,然后遍历 fileA 并在找到第一个匹配项时跳过所有剩余的模式(使用比 shellscript 更好的语言)
  • 好的,感谢您的帮助。 FileB 将其删除后,我确实使用 sort 对其进行了排序。 Grep -f 仍在运行,但 outputfile 仍然为空。啊。我会在稍后尝试您的解决方案。
【解决方案2】:

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

最好使用 Perl 或 Python 等完整的编程脚本语言。您将 File B 中的所有行放在一个散列中。取文件 A 中的每一行,检查第四个字段是否与散列中的某些内容匹配。

阅读几十万行?创建一个 10,000,000 条目哈希? Perl 可以在几分钟内解析这两者。

某事——在我的脑海中。你没有给我们太多的spects,所以我没有做任何测试:

#! /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 次。启动程序,去办公室厨房拿杯咖啡。 (嗯!非乳制奶精!)当您回到办公桌前时,一切都会完成。

【讨论】:

  • 在此之前我并没有真正用 perl 或 python 做过很多事情......是的。不过,我会试试你的脚本。谢谢您的帮助!我编辑了 OP,所以我包含了两个文件的样子。
  • 等等。我只是注意到这个东西实际上并没有输出任何东西。我必须在您的脚本中添加什么才能让它从通过的文件 A 中输出内容?抱歉,根本不懂 perl。
  • 这不满足 OP 的要求,即匹配“不必是完全匹配,所以如果文件 B 有 Blah 并且文件 A 说 Blah_blah,就可以了”。跨度>
  • @joe say 语句对于 Perl 来说是新的。这就像一个print 语句,最后会自动打印一个\n
  • @ruakh - 我认为“不准确”的要求更多是由于使用了grep 而不是初始要求。 距离有多近并没有真正的定义。我要说的主要观点是grep 在这种情况下很慢,因为它需要算法:时间随着两个文件中行数的乘积而增加。 BASH shell 功能强大,但也有其局限性。有时会开发出一种真正的脚本语言,这就是其中之一。
【解决方案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
    }
}

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

【讨论】:

  • 您好,感谢您的回答。文件 B 已经被处理成另一个文件,所以我不需要它。我现在就试试你的脚本,看看会发生什么。
  • 嗯,它确实有效......有点。这给了我一个 2.1MB 的输出文件,而我找到并编写的另一个脚本给了我一个 11MB 的输出文件(!!!)。我仍在尝试找出其中的区别。
  • 嗯,所以结果证明这个脚本停止了大约 1/5 到 1/4,而不是一直运行。这很奇怪。编辑:那是单行脚本。好像另一个人回来了……什么都没有。呵呵。
  • @Joe:听起来您的数据有一些不一致之处。脚本和上面的单行应该输出完全相同的结果。您能否向我发送更多示例数据或完整文件(最好在任何处理之前)?我会推荐像dropboxmediafire 这样的东西。
  • 嗯。所以只是为了它而不是做 awk -f script.awk fileB.txt fileA.txt 我把 awk -f script.awk fileA.txt fileB.txt... 这返回了与另一个完全相同的东西我找到的脚本。这很奇怪。我想如果您仍然需要文件,我可以将文件发送给您,但文件 B 必须被削减(否则抱歉,它是 400MB)。
【解决方案4】:

使用以下命令:

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

【讨论】:

    猜你喜欢
    • 2011-04-26
    • 2013-08-29
    • 1970-01-01
    • 2021-11-01
    • 2014-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多