如何从一个文件中获取属于另一个文件的范围列表中的值

时间:2018-08-14 15:17:15

标签: bash

我有一堆带有排序数值的文件,例如:

cat tag_1_file.val
234
551
626
cat tag_2_file.val
12
1023
1099

一个带有标签和值范围的文件可以满足我的需求。值首先按标记排序,然后按第二列,然后按第三排序。范围可能会重叠。

cat ranges.val
tag_1 200 300
tag_1 600 635
tag_2 421 443

以此类推。

因此,我尝试遍历带范围的文件,然后使用适当的标记查找文件中(每一行)范围内的所有值:

cat ~/blahblah/ranges.val | while read -a line;
#read line as array
do
cat ~/blahblah/${line[0]}_file.val | while read number;
#get tag name and cat the appropriate file
do
if [[ "$number" -ge "${line[1]}" ]] && [[ "$number" -le "${line[2]}" ]]
#check if current value fall into range
then
echo $number >> ${line[0]}.output
#toss the value that fall into interval to another file
elif [[ "$number" -gt "${line[2]}" ]]
then break
fi
done
done

但是对于包含100M +行的巨大文件,这两个嵌套的while循环非常慢。

我认为,必须有一种更有效的方式来做这些事情,我将不胜感激。

UPD:基于此示例的预期输出为:

cat file tag_1.output
234
626

4 个答案:

答案 0 :(得分:1)

我会写

while read -u3 -r tag start end; do 
    f="${tag}_file.val"
    if [[ -r $f ]]; then 
        while read -u4 -r num; do 
            (( start <= num && num <= end )) && echo "$num"
        done 4< "$f"
    fi
done 3< ranges.val

我正在故意在单独的文件描述符中读取文件,否则内部的while读取循环也会吞噬其余的“ ranges.val”。


bash同时读取循环非常慢。如果几分钟后有其他解决方法,我会回来的


这是GNU awk的答案(我认为需要一个相当新的版本)

gawk '
    @load "filefuncs"
    function read_file(tag, start, end,       file, number, statdata) {
        file = tag "_file.val"
        if (stat(file, statdata) != -1) {
            while (getline number < file) {
                if (start <= number && number <= end) print number
            }
        }
    }
    {read_file($1, $2, $3)}
' ranges.val

perl

perl -Mautodie -ane '
    $file = $F[0] . "_file.val";
    next unless -r $file;
    open $fh, "<", $file;
    while ($num = <$fh>) {
        print $num if $F[1] <= $num and $num <= $F[2]
    }
    close $fh;
' ranges.val 

答案 1 :(得分:1)

您是否尝试过以比Bash更有效的方式重新编码内部循环? Perl可能足够好:

while read tag low hi; do
    perl -nle "print if \$_ >= ${low} && \$_ <= ${hi}" \
            <${tag}_file.val >>${tag}.output
done <ranges.val

此版本在两个方面略有不同的行为-达到最高点后,循环就不会松开,即使输出文件为空,它也会被创建。如果那不是您想要的,就交给您!

答案 2 :(得分:1)

使用awk

的另一种实施方式效率不高
$ awk 'NR==FNR {t[NR]=$1; s[NR]=$2; e[NR]=$3; next} 
               {for(k in t)
                  if(t[k]==FILENAME) {
                     inout = t[k] "." ((s[k]<=$1 && $1<=e[k])?"in":"out");
                     print > inout;
                     next}}' ranges tag_1 tag_2

$ head tag_?.*

==> tag_1.in <==
234

==> tag_1.out <==
551
626

==> tag_2.out <==
12
1023
1099

请注意,我重命名了文件以使其与标签名称匹配,否则必须从文件名中添加标签提取。后缀“ .in”表示范围,“。out”表示范围。取决于文件的排序顺序。如果您有成千上万个标记文件,则添加另一个层来过滤掉每个标记的范围将加快该过程。现在,它会在范围内进行迭代。

答案 3 :(得分:0)

我为您提供了生物信息学的解决方案: 我们有完成这种任务的格式和工具。 称为.bed的格式用于描述染色体上的范围,但也应与您的标签一起使用。 此格式的最佳工具集是bedtools,这是闪电般快速的。 可以帮助您的特定工具是intersect

安装此工具后,将成为格式化工具数据的任务:

#!/bin/bash
#reformating your positions to .bed format; 
#1 adding the tag to each line
#2 repeating the position to make it a range 
#3 converting to tab-separation
awk -F $'\t' 'BEGIN {OFS = FS} {print FILENAME, $0, $0}' *_file.val | sed 's/_file.val//g' >all_positions_in_one_range_file.bed
#making your range-file tab-separated
sed 's/ /\t/g' ranges.val >ranges_with_tab.bed
#doing the real comparision of the ranges with bedtools
bedtools intersect -a all_positions_in_one-range_file.bed  -b ranges_with_tab.bed >all_positions_intersected.bed
#spliting the one result file back into files named by your tag
awk -F $'\t' '{print $2 >$1".out"}' all_positions_intersected.bed

或者,如果您更喜欢单线纸:

bedtools intersect -a <(awk -F $'\t' 'BEGIN {OFS = FS} {print FILENAME, $0, $0}' *_file.val | sed 's/_file.val//g') -b <(sed 's/ /\t/g' ranges.val) | awk -F $'\t' '{print $2 >$1".out"}'