我有一堆带有排序数值的文件,例如:
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
答案 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"}'