while与来自分割文件的输入并行循环

时间:2018-12-20 13:03:37

标签: bash while-loop parallel-processing

我坚持下去。因此,我的代码中有一个while读取循环,它花了很长时间,我想在许多处理器中运行它。但是,我想分割输入文件并运行14个循环(因为我有14个线程),每个分割的文件并行一个。问题是我不知道如何告诉while循环获取和使用哪个文件。

例如,在常规的while读取循环中,我将编写代码:

while read line
do
   <some code>
done < input file or variable...

但是在这种情况下,我想将上面的输入文件分成14个文件,并并行运行14个while循环,每个分割的文件一个。 我试过了:

split -n 14 input_file
find . -name "xa*" | \
        parallel -j 14 | \
        while read line
        do
        <lot of stuff>
        done

也尝试过

split -n 14 input_file
function loop {
            while read line
            do
                <lot of stuff>
            done
}
export -f loop
parallel -j 14 ::: loop 

但是我都不知道哪个文件是循环的输入,因此并行将理解“将每个xa *文件并并行放置到各个循环中”

输入文件的示例(字符串列表)

AEYS01000010.10484.12283
CVJT01000011.50.2173
KF625180.1.1799
KT949922.1.1791
LOBZ01000025.54942.57580

编辑

这是代码。 输出是一个表格(741100行),其中包含有关已经进行的DNA序列比对的一些统计信息。 循环使用一个带有DNA序列伪点的input_file(无折线,从500到〜45000行,从800Kb变化),逐行读取它,并查找数据库中这些伪点的每个对应的完整分类法(〜45000行) 。然后,它会做一些和/除。输出是.tsv,看起来像这样(序列“ KF625180.1.1799”的示例):

Rate of taxonomies for this sequence in %:        KF625180.1.1799 D_6__Bacillus_atrophaeus
Taxonomy %aligned number_ocurrences_in_the_alignment     num_ocurrences_in_databank    %alingment/databank
D_6__Bacillus_atrophaeus   50%     1       20      5%
D_6__Bacillus_amyloliquefaciens    50%     1       154     0.649351%



$ head input file  
AEYS01000010.10484.12283
CVJT01000011.50.217
KF625180.1.1799
KT949922.1.1791
LOBZ01000025.54942.57580

循环中还使用了两个其他文件。它们不是循环输入。 1)名为alnout_file的文件,该文件仅用于查找给定序列对数据库造成的命中(或比对)次数。它也是以前在此循环之外制作的。它的行数可以从单行到数千行不等。此处仅第1列和第2列很重要。 Column1是序列的名称,而col2是它在databnk中匹配的所有序列的名称。看起来像这样:

$ head alnout_file
KF625180.1.1799 KF625180.1.1799 100.0   431     0       0       1       431     1       431     -1      0
KF625180.1.1799 KP143082.1.1457 99.3    431     1       2       1       431     1       429     -1      0
KP143082.1.1457 KF625180.1.1799 99.3    431     1       2       1       429     1       431     -1      0    

2)一个数据库.tsv文件,其中包含对应于DNA序列的〜45000个分类法。每个分类法都在一行中:

$ head taxonomy.file.tsv
KP143082.1.1457 D_0__Bacteria;D_1__Firmicutes;D_2__Bacilli;D_3__Bacillales;D_4__Bacillaceae;D_5__Bacillus;D_6__Bacillus_amyloliquefaciens
KF625180.1.1799 D_0__Bacteria;D_1__Firmicutes;D_2__Bacilli;D_3__Bacillales;D_4__Bacillaceae;D_5__Bacillus;D_6__Bacillus_atrophaeus

因此,给定序列KF625180.1.1799。我之前将其与包含约45000个其他DNA序列的数据库进行比对,并获得了输出,其中包含与之匹配的序列的所有附件。循环的作用是找到所有这些序列的分类法,并计算出我之前提到的“统计量”。对我拥有的所有DNA序列配件都进行编码。

TAXONOMY=path/taxonomy.file.tsv
while read line
do
#find hits
        hits=$(grep $line alnout_file | cut -f 2)
        completename=$(grep $line $TAXONOMY | sed 's/D_0.*D_4/D_4/g')
        printf "\nRate of taxonomies for this sequence in %%:\t$completename\n"
        printf "Taxonomy\t%aligned\tnumber_ocurrences_in_the_alignment\tnum_ocurrences_in_databank\t%alingment/databank\n"

        #find hits and calculate the frequence (%) of the taxonomy in the alignment output
        # ex.: Bacillus_subtilis 33
        freqHits=$(grep "${hits[@]}" $TAXONOMY | \
                cut -f 2 | \
                awk '{a[$0]++} END {for (i in a) {print i, "\t", a[i]/NR*100, "\t", a[i]}}' | \
                sed -e 's/D_0.*D_5/D_5/g' -e 's#\s\t\s#\t#g' | \
                sort -k2 -hr)

        # print frequence of each taxonomy in the databank

        freqBank=$(while read line; do grep -c "$line" $TAXONOMY; done < <(echo "$freqHits" | cut -f 1))
        #print cols with taxonomy and calculations
        paste <(printf %s "$freqHits") <(printf %s "$freqBank") | awk '{print $1,"\t",$2"%","\t",$3,"\t",$4,"\t",$3/$4*100"%"}'

done < input_file

这需要大量的时间和解析,因此在一个处理器中运行大约12h即可完成所有45000个DNA序列登录。我想将input_file分割并在我拥有的所有处理器(14)中进行处理,因为这会花费时间。 谢谢大家对我这么耐心=)

6 个答案:

答案 0 :(得分:1)

作为替代方案,我进行了一次快速测试。

#! /bin/env bash
mkfifo PIPELINE             # create a single queue
cat "$1" > PIPELINE &       # supply it with records
{ declare -i cnt=0 max=14
  while (( ++cnt <= max ))  # spawn loop creates worker jobs
  do printf -v fn "%02d" $cnt
     while read -r line     # each work loop reads common stdin...
     do echo "$fn:[$line]"
        sleep 1
     done >$fn.log 2>&1 &   # these run in background in parallel
  done                      # this one exits
} < PIPELINE                # *all* read from the same queue
wait
cat [0-9][0-9].log

不需要split,但是需要mkfifo

很显然,请在内部循环中更改代码。

答案 1 :(得分:1)

这回答了您的要求,即如何并行处理从运行split获得的14个文件。但是,我不认为这是做您想做的事情的最佳方法-但是我们将需要您的一些答案。

因此,让我们制作一个百万行的文件并将其分为14个部分:

seq 1000000 > 1M
split -n 14 1M part-

这给了我14个名为part-aapart-an的文件。现在您的问题是如何并行处理这14个部分-(首先阅读最后一行):

#!/bin/bash

# This function will be called for each of the 14 files
DoOne(){
   # Pick up parameters
   job=$1
   file=$2
   # Count lines in specified file
   lines=$(wc -l < "$file")
   echo "Job No: $job, file: $file, lines: $lines"
}

# Make the function above known to processes spawned by GNU Parallel
export -f DoOne

# Run 14 parallel instances of "DoOne" passing job number and filename to each
parallel -k -j 14 DoOne {#} {} ::: part-??

示例输出

Job No: 1, file: part-aa, lines:    83861
Job No: 2, file: part-ab, lines:    72600
Job No: 3, file: part-ac, lines:    70295
Job No: 4, file: part-ad, lines:    70295
Job No: 5, file: part-ae, lines:    70294
Job No: 6, file: part-af, lines:    70295
Job No: 7, file: part-ag, lines:    70295
Job No: 8, file: part-ah, lines:    70294
Job No: 9, file: part-ai, lines:    70295
Job No: 10, file: part-aj, lines:    70295
Job No: 11, file: part-ak, lines:    70295
Job No: 12, file: part-al, lines:    70294
Job No: 13, file: part-am, lines:    70295
Job No: 14, file: part-an, lines:    70297

您通常会忽略 GNU Parallel -k参数-我只是添加了它,因此输出是按顺序排列的。

答案 2 :(得分:1)

您正在寻找--pipe。在这种情况下,您甚至可以使用经过优化的--pipepart(版本> 20160621):

export TAXONOMY=path/taxonomy.file.tsv
doit() {
while read line
do
#find hits
        hits=$(grep $line alnout_file | cut -f 2)
        completename=$(grep $line $TAXONOMY | sed 's/D_0.*D_4/D_4/g')
        printf "\nRate of taxonomies for this sequence in %%:\t$completename\n"
        printf "Taxonomy\t%aligned\tnumber_ocurrences_in_the_alignment\tnum_ocurrences_in_databank\t%alingment/databank\n"

        #find hits and calculate the frequence (%) of the taxonomy in the alignment output
        # ex.: Bacillus_subtilis 33
        freqHits=$(grep "${hits[@]}" $TAXONOMY | \
                cut -f 2 | \
                awk '{a[$0]++} END {for (i in a) {print i, "\t", a[i]/NR*100, "\t", a[i]}}' | \
                sed -e 's/D_0.*D_5/D_5/g' -e 's#\s\t\s#\t#g' | \
                sort -k2 -hr)

        # print frequence of each taxonomy in the databank

        freqBank=$(while read line; do grep -c "$line" $TAXONOMY; done < <(echo "$freqHits" | cut -f 1))
        #print cols with taxonomy and calculations
        paste <(printf %s "$freqHits") <(printf %s "$freqBank") | awk '{print $1,"\t",$2"%","\t",$3,"\t",$4,"\t",$3/$4*100"%"}'

done
}
export -f doit
parallel -a input_file --pipepart doit

这会将输入文件切成10 * ncpu个块(其中ncpu是CPU线程数),将每个块传递给doit,并行运行ncpu作业。

那就是说,我认为您的真正问题是产生了太多的程序:如果您用Perl或Python重写doit,我希望您会看到一个重大的提速。

答案 3 :(得分:1)

我认为在这里使用一堆grepawk命令是错误的方法-使用Perl或awk可能会更好。由于您没有提供任何示例文件,因此我使用以下代码生成了一些文件:

#!/bin/bash

for a in {A..Z} {0..9} ; do
   for b in {A..Z} {0..9} ; do
      for c in {A..Z} {0..9} ; do
         echo "${a}${b}${c}"
      done
   done
done > a

# Now make file "b" which has the same stuff but shuffled into a different order
gshuf < a > b

请注意,字母中有26个字母,因此,如果将数字0..9添加到字母中,则会得到36个字母数字,如果嵌套3个循环,则会得到36^3或46,656行,大致与您的文件大小匹配。文件a现在看起来像这样:

AAA
AAB
AAC
AAD
AAE
AAF

文件b如下所示:

UKM
L50
AOC
79U
K6S
6PO
12I
XEV
WJN

现在,我想遍历a,在b中找到相应的行。首先,我使用您的方法:

time while read thing ; do grep $thing b > /dev/null ; done < a

这需要 9分钟35秒

如果我现在在第一场比赛中退出grep,平均而言,我会在中间找到它,这意味着时间将减少一半,因为我以后不会继续不必要地阅读b找到我想要的。

time while read thing ; do grep -m1 $thing b > /dev/null ; done < a

这可以将时间缩短至 4分30秒

如果我现在使用awkb的内容读入一个关联数组(也称为哈希),然后读取a的元素并在b中找到它们像这样:

time awk 'FNR==NR{a[$1]=$1; next} {print a[$1]}' b a > /dev/null

现在运行时间为0.07秒。希望你对我开车的想法有所了解。我希望Perl可以同时执行此操作,并且还可以在循环中间为数学提供更多的表达功能。

答案 4 :(得分:0)

我希望这个小脚本可以帮助您:

function process {
    while read line; do
        echo "$line"
    done < $1
}

function loop {
    file=$1
    chunks=$2
    dir=`mktemp -d`
    cd $dir
    split -n l/$chunks $file
    for i in *; do
        process "$i" &
    done
    rm -rf $dir
}

loop /tmp/foo 14

它以指定数量的块(无分割行)并行地在指定文件上运行过程循环(使用&将每个调用置于后台)。希望它能帮助您入门。

答案 5 :(得分:0)

这可以为您完成工作,我对并行并不熟悉,而是使用本机bash生成过程&

function loop () {
  while IFS= read -r -d $'\n'
  do
    # YOUR BIG STUFF
  done < "${1}"
}

arr_files=(./xa*)
for i in "${arr_files[@]}"
do loop "${i}" &
done
wait