Bash 组合多个grep搜索并使我的脚本更高效

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的实例数,并将其导出到另一个提供摘要的

我有一个名为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...
到目前为止,脚本混乱不堪,无法使用

对于上述内容,我有以下几点:

#!/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执行以下步骤:

  • 只匹配ID线
  • 将匹配的输入行拆分为字段
  • 选择并打印所需的字段
  • 对筛选结果中的行进行排序
  • 数一数相邻的重复项
  • 对结果执行任何专门的格式设置
  • 例如:

    $ 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
      只过滤以
      ID开头的行。
      后跟1位(至少)
    • cut
      使用
      作为字段分隔符,仅输出字段1和2,从而删除 包括第二个
      之后的所有内容
    • sort
      为uniq工作的行排序
    • uniq
      打印其输入中以计数为前缀的每一行
    • awk
      part反转这些字段,并用
      =
      分隔打印它们
    如果ID的第一部分也可以包含字母,请将正则表达式的结尾改为
    [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将对一个大文件提供零置信度,那就请便吧。如果你对现有答案做了如此微小的改进,你可以随时编辑其中一个,或者至少给予表扬。(此外,你的是唯一一个