Bash 组合多个grep搜索并使我的脚本更高效
我有一个名为Type1.txt的文件,看起来像这样:Bash 组合多个grep搜索并使我的脚本更高效,bash,grep,counter,Bash,Grep,Counter,我有一个名为Type1.txt的文件,看起来像这样: $ cat Type1.txt ID.580.G3C0 TTTTTTTTTTT ID.580.G3C8 ATTATATC-AAA ID.580.GXC16 ATTATTTC-ACG-TTTTTCCTA ID.694.G9C3 ATTATATC-ACG-AAATCCTA ID.694.G9C3 etc... ID.580 = 3 ID.694 = 1 etc... 我想编写一个bash脚本来计算每个ID的实例数,并将其导出到另一个提供摘要的
$ cat Type1.txt
ID.580.G3C0
TTTTTTTTTTT
ID.580.G3C8
ATTATATC-AAA
ID.580.GXC16
ATTATTTC-ACG-TTTTTCCTA
ID.694.G9C3
ATTATATC-ACG-AAATCCTA
ID.694.G9C3
etc...
ID.580 = 3
ID.694 = 1
etc...
我想编写一个bash脚本来计算每个ID的实例数,并将其导出到另一个提供摘要的文件中,如下所示:
$ cat Type1.txt
ID.580.G3C0
TTTTTTTTTTT
ID.580.G3C8
ATTATATC-AAA
ID.580.GXC16
ATTATTTC-ACG-TTTTTCCTA
ID.694.G9C3
ATTATATC-ACG-AAATCCTA
ID.694.G9C3
etc...
ID.580 = 3
ID.694 = 1
etc...
到目前为止,脚本混乱不堪,无法使用
对于上述内容,我有以下几点:
#!/bin/bash
for Count in `grep -c "ID.580" Type1.txt; do
echo $Count=ID.580
done > Result.txt #Allows to count only for that single ID.
我有一千多个ID.XXX,这使得这段代码无法使用,因为在每次搜索中添加单个ID.XXX是不合理的。谢谢你的帮助
grep'^ID.[0-9][0-9][0-9]'输入文件|剪切-c1-6 |排序| uniq-c
有效吗?awk也可能有效
awk '/ID.580/{x++}END{print x}' test.txt
你可以把它放在一个for循环中
for i in ID.580 ID.694
do
awk '/'$i'/{x++}END{print x}' test.txt
done
TL;博士
考虑到您特定的语料库和分组策略,有多种方法可以获得您需要的结果。这里有两个备选解决方案,一个在awk中,另一个在Ruby中
GNU awk
一种方法是使用GNU awk执行以下步骤:
$ awk '/^ID/ {split($0, a, "."); print a[1] "." a[2]}' /tmp/foo |
sort | uniq --count | awk '{print $2 " = " $1}'
ID.580 = 3
ID.694 = 2
根据您在问题中提供的语料库,在我的系统上平均需要8毫秒。当然,一个更大的语料库需要更长的时间,但除非你有一个真正庞大的数据集,否则对于大多数目的来说,这应该足够快
红宝石
Ruby提供了我认为更优雅的解决方案,但实际上是比较慢的。这里的想法是将ID的相关部分存储为哈希键,每次遇到给定的ID时递增计数器。例如,考虑这个Ruby一个内衬:
$ ruby -ne 'BEGIN { id = Hash.new(0) }
id[$&] += 1 if /\AID\.\d+/
END { id.each_pair do |k,v| puts "#{k} = #{v}" end }' /tmp/foo
ID.580 = 3
ID.694 = 2
这个解决方案需要大约45毫秒来处理相同的语料库,所以我不建议仅仅为了转换输出而在awk管道上使用它。这样做的主要优点是,您有一个实际的数据结构(例如a),可以在功能更全面的程序中进行操作。Shell
下面的代码使用标准的UNIX实用程序,并且不假定ID的第二部分正好是3个字符,但将找到ID.1.123123
和ID.1234.123123
,并正确地只取第一个点分隔的部分。事实上
grep '^ID\.[0-9]' Type1.txt | cut -d . -f 1-2 | sort \
| uniq -c | awk '{ print $2" = "$1 }'
只过滤以grep
后跟1位(至少)ID开头的行。
使用cut
作为字段分隔符,仅输出字段1和2,从而删除 包括第二个
之后的所有内容
为uniq工作的行排序sort
打印其输入中以计数为前缀的每一行uniq
part反转这些字段,并用awk
分隔打印它们=
[0-9]
改为[0-9A-Z]
。比如说
管道输出
ID.580 = 3
ID.694 = 2
python
由于Python在生物学家中很受欢迎,您可能希望磨练Python技能:
from collections import Counter
counter = Counter()
with open('Type1.txt') as f:
for line in f:
if line.startswith('ID.'):
top_id = '.'.join(line.split('.', 2)[:2])
counter[top_id] += 1
for top_id, count in sorted(counter.items()):
print("%s = %d" % (top_id, count))
结果完全相同。以下是awk one liner:
$ awk -F. '$1=="ID"{a[$2,$3]++}END{for (i in a) {split(i,ind,SUBSEP); r[ind[1]]++}for (i in r) print "ID."i" = "r[i]}' file
ID.694 = 1
ID.580 = 3
下面是一个纯bash解决方案:
#!/bin/bash
while IFS=. read -r pre id code rest
do
[[ $pre == ID ]] || continue
[[ ${a[$id]} =~ \."$code"\. ]] || {
a[$id]="${a[$id]}.$code."
((count[$id]++));
}
done < file
for i in "${!count[@]}"
do
echo "ID.$i = ${count[$i]}"
done
$ ./script.sh
ID.580 = 3
ID.694 = 1
#/bin/bash
而IFS=。read-r前id代码rest
做
[$pre==ID]| |继续
[[${a[$id]}=~\.“$code”\.]\.\124;{
a[$id]=“${a[$id]}.$code。”
((计数[$id]+);
}
完成<文件
对于“${!count[@]}”中的i
做
echo“ID.$i=${count[$i]}”
完成
$./script.sh
ID.580=3
ID.694=1
grep相当宽容。另外,询问者只想查看id的前两个字段。@user1698774我已经重构了答案,以更好地解决您的输出格式,并为您提供一些解决问题可能采取的程序步骤的额外想法。祝你好运@随机字符串锚定是不必要的,因为“ID”不应该出现在核酸序列中,但它可能允许正则表达式引擎在更大的语料库上更快地失败;我决定将其添加为(可能过早的)优化。至于按ID分组,也已经解决了这个问题。祝你好运好办法。重新锚定ID,考虑从发布的输入:“ID.580.GXC16”的线-如果“GX”可能发生,所以可以“ID”。它可能不会有什么不同,因为它前面应该有一个“ID”,但对于软件来说,增加一些健壮性是很好的。PS:我很好奇为什么你选择了awk
而不是grep…|切…
?我相信这有一个很好的理由,所以我只是好奇一下。我已经试图澄清你的问题,但你可能需要做一些额外的工作来澄清你的意图。我是bash脚本的忠实粉丝——我一直在使用它,偶尔也会做一些相当复杂的事情。然而,这是我在Perl中要做的事情。在这种情况下,这将是完全微不足道的,逻辑也将是直截了当的。这类任务所需的Perl数量很容易从现成的示例中学习。如果你打算大量使用文本文件中的遗传数据,那么以后使用Perl将有助于将数据重新排列为不同工具所需的不同格式。是给我的,谢谢你们!我想跳到Python而不是Perl,建议?@user1698774如果你必须学习编程语言来解决这个问题,我建议使用Java;-)或者,如果这太无聊了,Haskell。您的解决方案与OP描述的输出格式不匹配。asker对输出说“类似于此”,并明确指出问题在于效率“我有1000多个ID.XXXX,使此代码无法使用”,但是,如果你必须任性地将这个逻辑正确的解决方案与你相当宽容的grep进行比较,grep将对一个大文件提供零置信度,那就请便吧。如果你对现有答案做了如此微小的改进,你可以随时编辑其中一个,或者至少给予表扬。(此外,你的是唯一一个