我已经在bash中编写了一个小程序,它将多个(选项卡)行(六列)转换为一行,其中包含12列,如下所示:
$1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12
input
Scaffold952 345718 345781 aug3.g8795.t1 . -
Scaffold952 346504 346534 aug3.g8795.t1 . -
Scaffold952 346721 346733 aug3.g8795.t1 . -
Scaffold952 348148 348241 aug3.g8795.t1 . -
output
Scaffold952 345718 345781 aug3.g8795.t1 . - 345718 345718 0 4 63,30,12,93 0,786,1003,2430
要完成此操作:
第11栏由$ 3 - $ 2组成;对于每6个列行,第12列由$ 2减去第一个$ 2值(345718)组成,并打印为csv。
我的代码:
#!/bin/bash
input=$1
output=$2
> $output
# functions
function joinArray { local IFS="$1"; shift; echo "$*"; }
# sort input
sort -k4,4 -k2,2 -o $input < $input
awk '{ print $4 }' $input | uniq | while read -r line; do
dup="$(grep -c $line $input)"
start="$(grep $line $input | awk 'NR==1 { print $2 }')"
records="$(grep $line $input | awk 'NR==1 { print $0 }')"
grep $line $input | {
while read -r record; do
blocksize+=($(awk '{ print $3 - $2 }' <<< "$record"))
blockstart+=($(awk -v var="$start" '{ print $2 - var }' <<< "$record"))
done
# combine input with arrays to form 12 col output
bed12[0]+=$(awk '{ print $1 }' <<< "$records")
bed12[1]+=$(awk '{ print $2 }' <<< "$records")
bed12[2]+=$(awk '{ print $3 }' <<< "$records")
bed12[3]+=$(awk '{ print $4 }' <<< "$records")
bed12[4]+=$(awk '{ print $5 }' <<< "$records")
bed12[5]+=$(awk '{ print $6 }' <<< "$records")
bed12[6]+=$(awk '{ print $2 }' <<< "$records")
bed12[7]+=$(awk '{ print $2 }' <<< "$records")
bed12[8]+='0'
bed12[9]+=$dup
bed12[10]+=$(joinArray $',' "${blocksize[@]}")
bed12[11]+=$(joinArray $',' "${blockstart[@]}")
joinArray $'\t' "${bed12[@]}" >> $output
}
done
到目前为止,我一直无法有效地运行此代码,我希望改进它,就像标准大小的文件(~30,000行)一样,需要花费三个小时才能完成。我不确定导致问题的原因可能是每次写入记录时打开/关闭输出文件;嵌套while循环;数组?这是一个糟糕的程序,语言不是一个合适的选择,或者这是一个这么大的文件(1.7 MB)?
答案 0 :(得分:3)
要记住的一些事情:
while read
循环内的所有内容都会为每个处理过的行运行一次。$(...)
执行fork()调用,创建一个全新的进程树条目,在该新进程中运行您附带的代码,读取其标准输出,并等待该进程完成。这是开销的很多。awk
- 虽然它是一个非常快速的语言解释器 - 或grep
,你实际上是在启动一个新程序:分叉它,动态加载它的库依赖关系,接线它的stdin和stdout ......这也是很多开销。>>foo
,打开输出文件以查找包含重定向的命令,然后在单个文件完成后再次关闭它。 (对于shell语言尤其如此; awk
缓存和重用文件描述符的一些>
实现,尤其包括GNU一个。)<<<
的bash实现涉及在磁盘上创建临时文件。如果在/dev/fd
是受支持的接口的平台上的紧密内循环中使用此构造,< <(printf '%s' "$foo")
因此可能比<<<"$foo"
更快 - 尽管我不建议实际执行此操作练习除非你需要它,因为bash的未来版本希望能够解决这种行为并用更容易阅读的语法做正确的事。那么,你能做什么?
对于awk
之类的工具,只有当您可以在多行输入中重复使用一个长时间运行的实例时才使用它们。
坦率地说,这是这里最重要的建议。将所有工作从bash移到单个awk
调用中,您将完成。
要将输入流解析为多个字段,请使用bash自己的内置函数:
read -r first_field second_field third_field ...
在一个封闭的外部范围内进行重定向,使用exec >foo
重定向整个程序的stdout(或整个子shell,如果在这样的上下文中执行),或者将>foo
放在后面done
关闭循环以在该循环的持续时间内重定向stdout。