【问题标题】:extract all lines from text file based on a given list of IDs根据给定的 ID 列表从文本文件中提取所有行
【发布时间】:2012-11-23 19:31:52
【问题描述】:

我有 2 个文本文件。 file1 包含一个 ID 列表:

11002
10995
48981
79600

file2:

10993   item    0
11002   item    6
10995   item    7
79600   item    7
439481  item    5
272557  item    7
224325  item    7
84156   item    6
572546  item    7
693661  item    7
.....

我正在尝试从 file2 中选择 ID(第一列)在 file1 中的所有行。目前,我正在做的是遍历第一个文件以创建一个正则表达式,例如:

^\b11002\b\|^\b10995\b\|^\b48981\b|^\b79600\b

然后运行:

grep '^11002\|^10995\|^48981|^79600' file2.txt

但是当file1 中的ID 数量太大(~2000)时,正则表达式会变得很长,grep 会变慢。还有其他方法吗?我正在使用 Perl + Awk + ​​Unix。

【问题讨论】:

  • 我已经看到很多答案了,但是如果你愿意的话,你可能会发现这里的 perl 代码可以适应:stackoverflow.com/questions/13713032/… 你需要添加一些东西来过滤掉 file2 中的第一个 col。

标签: perl unix awk


【解决方案1】:

使用hash table。它可能是内存密集型的,但查找是在恒定时间内进行的。这是一个高效且正确的过程——不是唯一的,而是高效且正确的——用于创建哈希表,使用file1 作为键,file2 用于在哈希表中查找键。如果键在哈希表中,则该行将打印到标准输出:

#!/usr/bin/env perl

use strict;
use warnings;

open FILE1, "< file1" or die "could not open file1\n";
my $keyRef;
while (<FILE1>) {
   chomp;
   $keyRef->{$_} = 1;
}
close FILE1;

open FILE2, "< file2" or die "could not open file2\n";
while (<FILE2>) {
    chomp;
    my ($testKey, $label, $count) = split("\t", $_);
    if (defined $keyRef->{$testKey}) {
        print STDOUT "$_\n";
    }
}
close FILE2;

在 Perl 中有很多方法可以做同样的事情。也就是说,我重视清晰和明确而不是花哨的晦涩,因为您永远不知道何时必须回到 Perl 脚本并进行更改,而且它们很难管理,因为它是。一个人的意见。

【讨论】:

  • TMTOWTDI: my %id; my @keys = do { open my $fh, '&lt;', 'file1'; &lt;$fh&gt; }; @id{@keys} = (); … if(exists $id{$testKey}) { … }
【解决方案2】:
awk 'NR==FNR{tgts[$1]; next} $1 in tgts' file1 file2

看:

$ cat file1
11002
10995
48981
79600
$ cat file2
10993   item    0
11002   item    6
10995   item    7
79600   item    7
439481  item    5
272557  item    7
224325  item    7
84156   item    6
572546  item    7
693661  item    7
$ awk 'NR==FNR{tgts[$1]; next} $1 in tgts' file1 file2
11002   item    6
10995   item    7
79600   item    7

【讨论】:

  • 它只打印最后一行。这是79600 item 7
  • 那么您的 file1 仅包含 79600 作为密钥,或者您的文件已损坏,或者您错误地复制/粘贴了我的脚本,或者您使用的是旧的、损坏的 awk。 awk --version 告诉你什么?我更新了答案以显示脚本正常工作。
  • 这个没有问题。
  • 我在Cygwin (Windows) 和Linux 上测试了大部分答案,发现结果各不相同(仅在一个平台上显示79600 item 7)所以要小心。我认为grep -f f1 f2 是这里最优雅的解决方案@EdMorton @jjennifer
  • @sudo_O 但如果 f1 中的数字出现在 f2 中的不良位置,则 grep 不会找到错误匹配项,例如如果 f1 在字段 1 中包含 10 而 f2 在字段 1 中包含 100,或者在其他字段中包含 10。
【解决方案3】:

我建议使用专门为此而设计的工具。使用连接命令。执行“man join”以获取更多信息。

linux_prompt> join file1 file2
11002 item 6
10995 item 7
79600 item 7

【讨论】:

  • 但是需要对文件进行排序。
  • 如果 OP 认为结果按排序顺序是可以的:join &lt;(sort file1) &lt;(sort file2)
【解决方案4】:

使用grep

$ grep -f f1 f2
11002   item    6
10995   item    7
79600   item    7

注意:我在多个系统上测试了很多建议的答案,有些只显示最后一个匹配 79600 item 7!?

【讨论】:

  • 如果 ID 出现在错误的列中,则会得到误报。
  • @JoshY。在 cygwin grep -f f1 f2 上只显示最后一场比赛,在 Linux 上测试和它的罚款..
【解决方案5】:

将第一个文件的所有元素加载到哈希中。 对于第二个文件的每一行, 使用正则表达式^(\d*) 提取数字 如果哈希包含提取的数字,则打印它

【讨论】:

    【解决方案6】:

    使用进程替换将 file1 中的 ID 转换为正则表达式:

    grep -f <(sed 's/.*/^&\\b/' file1) file2
    

    我假设您使用的是 bash 或类似功能的 shell

    【讨论】:

      【解决方案7】:

      简单的 perl 解决方案是使用散列并计算寻找的数字的出现次数。

      perl -lanwe 'print if $a{$F[0]}++ == 1;' file1.txt file2.txt
      

      我从您的示例数据中得到以下输出:

      11002   item    6
      10995   item    7
      79600   item    7
      

      请注意,您需要在命令行中以正确的顺序使用文件。

      这将打开并读取输入文件名 (-n),将行 (-a) 自动拆分为 @F,然后打印每一行,如果该数字的哈希值等于 1 . 如果要打印file2中的多个值,只需将== 1更改为&gt;= 1即可。

      注意++ 运算符是在相等比较完成后应用的。

      【讨论】:

        猜你喜欢
        • 2018-10-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多