在bash中对没有uniq的列进行排序和计数

时间:2015-06-01 13:51:10

标签: linux bash sorting count sh

我想使用bash只添加第一列的计数,而不执行uniq,如下所示:

输入:

58311s2727  NC_000082.6 100.00  50  
58311s2727  NC_000083.6 100.00  60
58311s2727  NC_000084.6 100.00  70
58310s2691  NC_000080.6 100.00  30
58310s2691  NC_000081.6 100.00  20
58308s2441  NC_000074.6 100.00  50

输出:

3  58311s2727   NC_000082.6 100.00  50  
3  58311s2727   NC_000083.6 100.00  60
3  58311s2727   NC_000084.6 100.00  70
2  58310s2691   NC_000080.6 100.00  30
2  58310s2691   NC_000081.6 100.00  20
1  58308s2441   NC_000074.6 100.00  50

我试过了:

sort input.txt | cut -f1 | uniq -c

但输出不是我想要的。我想知道是否有简单的方法来解决这个问题。

3 个答案:

答案 0 :(得分:1)

使用排序输入,您只需使用awk,捕获具有相同键的行集,并在键更改时打印上一个输出。处理EOF有点混乱;你必须重复打印。你可以编写一个awk函数来进行打印,但这对于这么简单的事情来说几乎是过度的。

script.awk

$1 != old_key { if (n_keys > 0) for (i = 0; i < n_keys; i++) print n_keys, saved[i]; n_keys = 0 }
{ saved[n_keys++] = $0; old_key = $1 }
END { if (n_keys > 0) for (i = 0; i < n_keys; i++) print n_keys, saved[i] }

示例运行

对于示例输入input.txt(已经分组),输出为:

$ awk -f script.awk input.txt
3 58311s2727  NC_000082.6 100.00  50  
3 58311s2727  NC_000083.6 100.00  60
3 58311s2727  NC_000084.6 100.00  70
2 58310s2691  NC_000080.6 100.00  30
2 58310s2691  NC_000081.6 100.00  20
1 58308s2441  NC_000074.6 100.00  50
$

如果您希望对其进行排序,请先对其进行排序:

$ sort input.txt | awk -f script.awk
1 58308s2441  NC_000074.6 100.00  50
2 58310s2691  NC_000080.6 100.00  30
2 58310s2691  NC_000081.6 100.00  20
3 58311s2727  NC_000082.6 100.00  50  
3 58311s2727  NC_000083.6 100.00  60
3 58311s2727  NC_000084.6 100.00  70
$

请注意,除了其他优点之外,这可以处理来自管道的数据,因为它不需要处理文件两次,这与当前接受的至少一个其他解决方案不同。它只在内存中保留尽可能多的行,因为在公共密钥的最大组中存在行,因此即使是相当大的文件也不太可能对系统上的内存造成压力。 (sort可能会比awk承担更多的内存负载。)

script2.awk

使用函数和一些空格,代码变为:

function dump_keys(    i) {
    if (n_keys > 0)
    {
        for (i = 0; i < n_keys; i++)
            print n_keys, saved[i]
    }
    n_keys = 0
}
$1 != old_key { dump_keys() }
              { saved[n_keys++] = $0; old_key = $1 }
END           { dump_keys() }

变量i是函数的本地变量(awk的怪癖)。我可以简单地从参数列表中省略它,因为脚本中的其他地方没有使用i

这产生与script.awk相同的输出。

答案 1 :(得分:0)

如果没有uniq,您必须阅读输入两次。在纯BASH中有一些方法可以做到这一点,但是当我切换到像Python 2这样的正确脚本语言时:

import codecs
from collections import Counter

filename='...'
encoding='...' # file encoding

counter = Counter()
with codecs.open(filename, 'r', encoding) as fh:
   for line in fh:
       parts = line.split(' ')
       counter[parts[0]] += 1

with codecs.open(filename, 'r', encoding) as fh:
   for line in fh:
       parts = line.split(' ')
       count = counter[parts[0]]
       print '%d%s' % (count, line),

答案 2 :(得分:0)

我会在awk中这样做。但正如Aaron所说,它需要两次读取输入,因为你第一次击中特定的一行时,你不知道有多少次它会击中它。

printf

第一次通过文件,用第一个字段的计数器填充数组。然后它再次进行,打印计数和每一行。

您可以调整$ declare -A a $ while read word therest; do ((a[$word]++)); done < inputfile $ while read word therest; do printf "%5d\t%s\t%s\n" "${a[$word]}" "$word" "$therest"; done < inputfile 语句以满足您的格式要求(可能将其替换为declare -A)。

如果你不想使用awk,并且真的希望它在bash中本地工作,你可以使用几个带有for循环的单行来实现几乎相同的结果:

$a

awk是必需的,因为c.on('data', function(data) { if (data.toString().charCodeAt(0) === 3) { c.destroy(); } }); 需要是一个关联数组,每行的第一个单词作为键。另一方面,SaveToFile将每个数组视为关联数组。请注意,此解决方案不会保留您的空白。