慢速bash脚本在输入文件的每一行上执行sed表达式

时间:2015-12-08 17:38:27

标签: bash parsing sed fasta

我有一个简单的bash脚本如下

#!/bin/bash
#This script reads a file of row identifiers separated by new lines 
# and outputs all query FASTA sequences whose headers contain that identifier.
# usage filter_fasta_on_ids.sh fasta_to_filter.fa < seq_ids.txt; > filtered.fa


while read SEQID; do
    sed -n -e "/$SEQID/,/>/ p" $1 | head -n -1
done

fasta文件具有以下格式:

> HeadER23217;count=1342
ACTGTGCCCCGTGTAA
CGTTTGTCCACATACC
>ANotherName;count=3221
GGGTACAGACCTACAC
CAACTAGGGGACCAAT

修改更改了标题名称,以便更好地显示文件中的实际结构

我上面制作的脚本确实正确过滤了文件,但速度非常慢。我的输入文件有~20,000,000行包含〜4,000,000个序列,我有一个80,000个标题列表,我想要过滤。有没有更快的方法使用bash / sed或其他工具(如python或perl?)上面的脚本需要几个小时才能完成?

3 个答案:

答案 0 :(得分:1)

您正在扫描大文件80k次。我会建议使用不同的工具采用不同的方法:awk。将选择列表加载到散列映射(awk数组)中,并在扫描大文件时(如果任何序列与print匹配)。

例如

$ awk -F"\n" -v RS=">" 'NR==FNR{for(i=1;i<=NF;i++) a["Sequence ID " $i]; next}
                        $1 in a' headers fasta         

-F"\n"标志将输入文件中的field separator设置为新行。 -v RS=">"将记录分隔符设置为">"

Sequence ID 1
ACTGTGCCCCGTGTAA
CGTTTGTCCACATACC

Sequence ID 4
GGGTACAGACCTACAT
CAACTAGGGGACCAAT

头文件包含

$ cat headers
1
4

并且fasta文件包含更多相同格式的记录。

如果您的标题已包含“序列ID”前缀,请按此调整代码。我没有为大文件测试这个,但是应该比你的代码快得多,只要你没有内存限制来保存80K大小的数组。在这种情况下,将标题拆分为多个部分并合并结果应该是微不足道的。

要允许任何格式的标题并使结果文件成为有效的FASTA文件,您可以使用以下命令:

awk -F"\n" -v RS=">" -v ORS=">" -v OFS="\n" 'NR==FNR{for(i=1;i<=NF;i++) a[$i]; next} $1 in a' headers fasta > out

ORSOFS标志设置输出字段和记录分隔符,在这种情况下与输入的fasta文件相同。

答案 1 :(得分:0)

你应该利用这个事实(你没有明确说明,但我认为)巨大的fasta文件按顺序包含序列(按ID排序)。

我还假设头文件按ID排序。如果不是这样,那就这样做 - 排序80k整数并不昂贵。

当两者都被分类时,它归结为通过两个文件的单个同时线性扫描。而且由于它在恒定内存中运行,因此与其他awk示例不同,它可以使用任何大小。我在python中给出了一个例子,因为我对awk中的手动迭代感到不舒服。

import sys

fneedles = open(sys.argv[1])
fhaystack = open(sys.argv[2])

def get_next_id():
    while True:
        line = next(fhaystack)
        if line.startswith(">Sequence ID "):
            return int(line[len(">Sequence ID "):])

def get_next_needle():
    return int(next(fneedles))

try:
    i = get_next_id()
    j = get_next_needle()
    while True:
        if i == j:
            print(i)
        while i <= j:
            i = get_next_id()
        while i > j:
            j = get_next_needle()
except StopIteration:
    pass

当然它有点冗长,但它在我的旧机器上在大约10秒钟内找到80k的4M序列(339M输入)。 (它也可以用awk重写,这可能会快得多)。我用这种方式创建了fasta文件:

for i in range(4000000):
    print(">Sequence ID {}".format(i))
    print("ACTGTGCCCCGTGTAA")
    print("ACTGTGCCCCGTGTAA")
    print("ACTGTGCCCCGTGTAA")
    print("ACTGTGCCCCGTGTAA")

标题(“针”)这样:

import random

ids = list(range(4000000))
random.shuffle(ids)
ids = ids[:80000]
ids.sort()
for i in ids:
    print(i)

答案 2 :(得分:0)

它很慢,因为当你可以读取一次并处理所有模式时,你正在读同一个文件几次。因此,您需要生成一个sed脚本,其中包含每个ID的语句和/>/b以替换head -n -1

while read ID; do
    printf '/%s/,/>/ { />/b; p }\n' $ID;
done | sed -n -f - data.fa