使用索引文件从文本文件中打印许多特定行

时间:2016-11-14 19:05:09

标签: bash unix awk sed

我有一个超过1亿行的大型文本文件,名为reads.fastq。此外,我还有另一个名为takeThese.txt的文件,其中包含应打印的文件reads.fastq中的行号(每行一个)。

目前我使用

awk 'FNR == NR { h[$1]; next } (FNR in h)' takeThese.txt reads.fastq > subsample.fastq

显然需要很长时间。有没有办法使用存储在另一个文件中的行号从文本文件中提取行?如果takeThese.txt文件将被排序,它会加快速度吗?

编辑:

我所拥有的文件的几个示例行:

reads.fastq

@HWI-1KL157:36:C2468ACXX
TGTTCAGTTTCTTCGTTCTTTTTTTGGAC
+
@@@DDDDDFF>FFGGC@F?HDHIHIFIGG
@HWI-1KL157:36:C2468ACXX
CGAGGCGGTGACGGAGAGGGGGGAGACGC
+
BCCFFFFFHHHHHIGHHIHIJJDDBBDDD
@HWI-1KL157:36:C2468ACXX
TCATATTTTCTGATTTCTCCGTCACTCAA

takeThese.txt

5
6
7
8

这样输出看起来像这样:

@HWI-1KL157:36:C2468ACXX
CGAGGCGGTGACGGAGAGGGGGGAGACGC
+
BCCFFFFFHHHHHIGHHIHIJJDDBBDDD

编辑:建议脚本的比较:

$ time perl AndreasWederbrand.pl takeThese.txt reads.fastq  > /dev/null

real    0m1.928s
user    0m0.819s
sys     0m1.100s

$ time ./karakfa  takeThese_numbered.txt reads_numbered.fastq  > /dev/null

real    0m8.334s
user    0m9.973s
sys     0m0.226s

$ time ./EdMorton takeThese.txt reads.fastq  > /dev/null

real    0m0.695s
user    0m0.553s
sys     0m0.130s

$ time ./ABrothers  takeThese.txt reads.fastq  > /dev/null

real    0m1.870s
user    0m1.676s
sys     0m0.186s

$ time ./GlenJackman takeThese.txt reads.fastq  > /dev/null

real    0m1.414s
user    0m1.277s
sys     0m0.147s

$ time ./DanielFischer takeThese.txt reads.fastq  > /dev/null

real    0m1.893s
user    0m1.744s
sys     0m0.138s

感谢所有的建议和努力!

7 个答案:

答案 0 :(得分:5)

您问题中的脚本将非常快,因为它只是对数组h中当前行号的哈希查找。除非你想打印reads.fastq中的最后一个行号,否则它会更快,因为它在打印完最后一个所需的行号后退出,而不是继续读取其余的reads.fastq:

awk 'FNR==NR{h[$1]; c++; next} FNR in h{print; if (!--c) exit}' takeThese.txt reads.fastq

你可以在delete h[FNR];之后输入一个print;以减少数组大小,因此可以加快查找时间但是如果这样可以真正提高性能,那么因为数组访问是一个哈希查找因此速度非常快,因此添加delete可能最终会降低整个脚本的速度。

实际上,这会更快,因为它避免了对两个文件中的每一行测试NR == FNR:

awk -v nums='takeThese.txt' '
    BEGIN{ while ((getline i < nums) > 0) {h[i]; c++} }
    NR in h{print; if (!--c) exit}
' reads.fastq

是否更快或者@glennjackman发布的脚本更快取决于takeThese.txt中的行数以及它们发生的reads.fastq的结尾有多接近。由于Glenns读取整个reads.fastq,无论takeThese.txt的内容是什么,它都将在大约恒定的时间内执行,而我的将在读取结束后显着更快。在takeThese.txt中发生最后一个行号。 e.g。

$ awk 'BEGIN {for(i=1;i<=100000000;i++) print i}' > reads.fastq

$ awk 'BEGIN {for(i=1;i<=1000000;i++) print i*100}' > takeThese.txt

$ time awk -v nums=takeThese.txt '
    function next_index() {
        ("sort -n " nums) | getline i
        return i
    }
    BEGIN { linenum = next_index() }
    NR == linenum { print; linenum = next_index() }
' reads.fastq > /dev/null
real    0m28.720s
user    0m27.876s
sys     0m0.450s

$ time awk -v nums=takeThese.txt '
    BEGIN{ while ((getline i < nums) > 0) {h[i]; c++} }
    NR in h{print; if (!--c) exit}
' reads.fastq > /dev/null
real    0m50.060s
user    0m47.564s
sys     0m0.405s

$ awk 'BEGIN {for(i=1;i<=100;i++) print i*100}' > takeThat.txt

$ time awk -v nums=takeThat.txt '
    function next_index() {
        ("sort -n " nums) | getline i
        return i
    }
    BEGIN { linenum = next_index() }
    NR == linenum { print; linenum = next_index() }
' reads.fastq > /dev/null
real    0m26.738s
user    0m23.556s
sys     0m0.310s

$ time awk -v nums=takeThat.txt '
    BEGIN{ while ((getline i < nums) > 0) {h[i]; c++} }
    NR in h{print; if (!--c) exit}
' reads.fastq > /dev/null
real    0m0.094s
user    0m0.015s
sys     0m0.000s

但你可以充分利用两个世界:

$ time awk -v nums=takeThese.txt '
    function next_index() {
        if ( ( ("sort -n " nums) | getline i) > 0 ) {
            return i
        }
        else {
            exit
        }
    }
    BEGIN { linenum = next_index() }
    NR == linenum { print; linenum = next_index() }
' reads.fastq > /dev/null
real    0m28.057s
user    0m26.675s
sys     0m0.498s


$ time awk -v nums=takeThat.txt '
    function next_index() {
        if ( ( ("sort -n " nums) | getline i) > 0 ) {
            return i
        }
        else {
            exit
        }
    }
    BEGIN { linenum = next_index() }
    NR == linenum { print; linenum = next_index() }
' reads.fastq > /dev/null
real    0m0.094s
user    0m0.030s
sys     0m0.062s

如果我们假设takeThese.txt已经排序可以简化为:

$ time awk -v nums=takeThese.txt '
    BEGIN { getline linenum < nums }
    NR == linenum { print; if ((getline linenum < nums) < 1) exit }
' reads.fastq > /dev/null
real    0m27.362s
user    0m25.599s
sys     0m0.280s

$ time awk -v nums=takeThat.txt '
    BEGIN { getline linenum < nums }
    NR == linenum { print; if ((getline linenum < nums) < 1) exit }
' reads.fastq > /dev/null
real    0m0.047s
user    0m0.030s
sys     0m0.016s

答案 1 :(得分:2)

认为问题中的解决方案将takeThese.txt中的所有行存储到数组h []中,然后对于reads.fastq中的每一行,在h []中执行线性搜索行号。

在不同语言中有几个简单的改进。如果你对java不熟悉,我会尝试perl。

基本上你应该确保takeThese.txt被排序,然后只需要一行读取reads.fastq,扫描一个与takeThese.txt中下一个行号相匹配的行号,然后弹出并继续。< / p>

由于行的长度不同,您别无选择,只能扫描换行符(基本for each line - 在大多数语言中构造)。

perl中的示例,快速而脏,但可以正常工作

open(F1,"reads.fastq");
open(F2,"takeThese.txt");
$f1_pos = 1;
foreach $index (<F2>) {
   while ($f1_pos <= $index) {
      $out = <F1>; $f1_pos++;
   } 
   print $out;
}

答案 2 :(得分:2)

我在awk中看到的问题是,您要将要提取的所有行号加载到数组中,然后,对于每一行,您需要访问该数组。

我确信in关键字必须按照循环遍历数组的每个元素并将该索引的值与FNR值进行比较...

因此,如果您要提取1,000,000行,则reads.fastq的每一行都需要遍历要提取的1,000,000行! 100,000,000(reads.fastq行)X 1,000,000(查找数组长度)= 1e+14。这是很多查找。

再次awks in关键字可以做各种花哨的技巧和有效的事情,但最后你应该明白为什么这不起作用。

一种方法是使用包含我们想要的当前行的变量,一个索引变量来跟踪我们在查找数组中的位置,以及一个max变量来查看我们是否可以停止处理文件!这样我们只执行N数组查找,每行一个,我们想要的每一行,其余的时间我们正在将FNR与变量进行比较,这应该更快。另外,在打印出我们想要的最后一行之后,我们停止执行。

显然,这要求我们有一个我们想要提取的行的排序列表。

readthese是您的"takeThese.txt"list.txt是一个文件,其中行的编号为1 - 1,000,000`

awk 'BEGIN{i=1; max=1;} FNR==NR{ if($1 != ""){h[max]=$1;  max++; next}} { if(!l){l=h[i]; i++; } if( FNR == l ){ print $0; l=h[i]; i++; if(i == max){  exit; } } }'

以更易读的格式

 awk '
    BEGIN{i=1; max=1;}

    FNR==NR{ 
        if($1 != ""){
            h[max]=$1;  max++; next
        }
    } 
    { 
        if(!l){
            l=h[i]; i++;
        }

        if( FNR == l ){ 
            print $0;
            l=h[i];
            i++;
            if(i == max){
              exit;
            }
        } 
    }' readthese list.txt

i是我们h数组中的当前位置,我们存储了要提取的行。 max基本上是h数组的长度,当i == h我们知道我们可以停止时。 l是我们要提取的下一行的值。

编辑:如果您需要对行文件进行排序,可以将readthese替换为<(sort -n readthese)

答案 3 :(得分:2)

我会尝试其中一个

  1. 可能会导致误报:

    cat -n reads.fastq | grep -Fwf takeThese.txt | cut -d$'\t' -f20
    
  2. 需要{bash,ksh,zsh}之一:

    sed -n -f <(sed 's/$/p/' takeThese.txt) reads.fastq
    
  3. 这类似于Andreas Wederbrand的perl答案,在awk中实现

    awk -v nums=takeThese.txt '
        function next_index() {
            ("sort -n " nums) | getline i
            return i
        }
        BEGIN { linenum = next_index() }
        NR == linenum { print; linenum = next_index() }
    ' reads.fastq
    
  4. 但是,如果你处理大量数据,文本处理工具需要时间。您的另一个选择是将数据导入适当的数据库并使用SQL来提取它:数据库引擎是为这种东西构建的。

答案 4 :(得分:2)

由于我感冒了并且感到无聊,我测试了一些尝试加快原始解决方案的方法。测试文件:

$ awk 'BEGIN {for(i=1;i<=100000000;i++) print i}' > reads.fastq 

$ awk 'BEGIN {for(i=1;i<=1000000;i++) print i*100}' > takeThat.txt

第一个文件只是1到100000000之间的数字。它不代表真实数据,但我对awk解决方案之间的执行时间感到好奇所以我认为真实数据只会将结果时间乘以常数(之前)记忆开始耗尽了。)

第二个文件表示第一个文件的均匀分布命中率为1%:

100
200
300
...

首先,OP的原始剧本:

$ time awk 'FNR==NR {h[$1]; next} (FNR in h)' takeThese.txt reads.fastq > /dev/null

real    0m52.901s
user    0m52.596s
sys     0m0.284s

我的解决方案:

BEGIN {
    j=1
    while((getline a[++i] < "takeThese.txt") > 0 );  # row numbers to a
} 
NR<a[j] { next }                                     # skip rows before next match
j++                                                  # print and iterate j
j==i { exit }                                        # exit after last hit

定时运行:

$ time awk -f program.awk reads.fastq > /dev/null

real    0m25.894s
user    0m25.676s
sys     0m0.208s

预计将订购行号文件takeThese.txt

答案 5 :(得分:2)

派对迟到了,但这也可能是一个快速的选择。它利用join的原始速度,但需要将匹配字段转换为字典顺序。

$ join <(awk '{printf "%09d\n", $1}' pick.list) <(nl -w9 -ba -nrz big.file) | 
  cut -d' ' -f2-

预处理您的选择列表以添加前导零,为您的大文件添加前导零(相同宽度)的行号,假设您的选择列表按数字顺序排列,否则先排序。

使用您自己的文件名更改“pick.list”和“big.file”的文件名。此外,如果大文件的行数超过999,999,999,请相应调整宽度(“%09”和“w9”)。

如果你试试这个,请发布你的时间。我的猜测是它会比awk替代品快得多。

nl选项

  

w9数字宽度为9
  ba在文本正文中为空行添加数字   带有前导零的nrz格式编号,右对齐,即000000001

答案 6 :(得分:0)

reads.fastq中的线条长度是否相同?

如果是这样,Java或任何其他语言的简单算法可以采用takeThese.txt中的每个行号,并通过将行号乘以行长来找到行在reads.fastq中开始的位置。

如果没有,那么找到正确行的唯一方法是计算换行符,这意味着要读取每个字符。这仍然可能比awk更快,它肯定有助于对行号进行排序。