如何让 uniq 只考虑第一个字段?

如何让 uniq 只考虑第一个字段?

我正在使用 FreeBSD 3.2-RELEASE

如果我有一些已排序的文本,例如此last输出 -

zikla13:Oct:20:22:34
zikla13:Oct:5:00:31
zikla14:Oct:17:22:01
zikla14:Oct:12:23:35
zikla14:Oct:12:23:34
zikla14:Oct:12:00:11
zikla14:Oct:11:23:52
zikla14:Oct:5:22:22
zilka13:Oct:13:23:48
zilka13:Oct:11:00:28
zilka13:Oct:9:22:40

— 有没有办法只uniq -c考虑第一个字段(也许用-s)?在这种情况下,输出应该是这样的:

2 zikla13:Oct:20:22:34
6 zikla14:Oct:17:22:01
3 zilka13:Oct:13:23:48

或者使用其他方式awk

答案1

使用 GNU uniq,它支持以下-w选项:

$ cat data
zikla13:Oct:20:22:34
zikla13:Oct:5:00:31
zikla14:Oct:17:22:01
zikla14:Oct:12:23:35
zikla14:Oct:12:23:34
zikla14:Oct:12:00:11
zikla14:Oct:11:23:52
zikla14:Oct:5:22:22
zilka13:Oct:13:23:48
zilka13:Oct:11:00:28
zilka13:Oct:9:22:40
$ uniq -c -w7 data
  2 zikla13:Oct:20:22:34
  6 zikla14:Oct:17:22:01
  3 zilka13:Oct:13:23:48

正如评论中指出的那样,这假设第一个字段始终是七个字符,在您的示例中是这样的,但如果在现实生活中不是这样,我认为没有办法用 uniq 来做到这一点(另外如果你没有 GNU uniq,甚至-w不起作用),所以这里有一个 perl 解决方案:

$ perl -ne '/(.*?):(.*)/;unless (exists $x{$1}){$x{$1}=[0,$2];push @x, $1};$x{$1}[0]++;END{printf("%8d %s:%s\n",$x{$_}[0],$_,$x{$_}[1]) foreach @x}' <data
   2 zikla13:Oct:20:22:34
   6 zikla14:Oct:17:22:01
   3 zilka13:Oct:13:23:48

工作原理如下:

$ perl -ne

运行 perl,不是默认打印每一行,并使用下一个参数作为脚本。

/(.*?):(.*)/

将输入行分成第一个冒号之前的内容和第一个冒号之后的内容,并且$1.$2split这里也可以起作用。

unless (exists $x{$1}){$x{$1}=[0,$2];push @x, $1}

哈希%x将用于唯一化行和数组@x以保持它们的顺序(您可以只使用sort keys %x,但假设 perlsort将以与输入相同的方式排序。)因此,如果我们从未见过当前“键”(第一个冒号之前的内容),则初始化键的哈希条目并将键推送到@x。每个键的哈希条目都是一个包含计数和冒号后看到的第一个值的双元素数组,因此输出可以包含该值。

$x{$1}[0]++

增加计数。

END{

启动一个将在读取所有输入后运行的块。

printf("%8d %s:%s\n",$x{$_}[0],$_,$x{$_}[1])

打印计数,用空格填充,一个空格,“键”,一个冒号,以及冒号后面的内容。

foreach @x}

对看到的每个键执行此操作,按顺序结束 END 块。

<data

从当前目录中名为 data 的文件中读取以获取输入。如果您有其他命令或管道生成数据,您也可以直接将其导入 perl。

答案2

我会使用awk。过滤并计算第一个冒号分隔的字段,当它改变或我们命中 EOF 时,打印整个之前保存的行并计数:

awk -F: '!seen[$1]++ { line[$1]=$0; if(prev){printf "%d\t%s\n",seen[prev],line[prev]}; prev=$1} END {if(prev){printf "%d\t%s\n",seen[prev],line[prev]}}' data

awk脚本可以像这样扩展:

# Count the occurrences of the first field. If first time then...
!seen[$1]++ {
    # save the line
    line[$1]=$0;
    # maybe print the previous line
    if (prev) {
        printf "%d\t%s\n", seen[prev], line[prev]
    };
    prev=$1
}

# End of file, so print any previous line we have got saved
END {
    if (prev) {
        printf "%d\t%s\n", seen[prev], line[prev]
    }
}

如果您可以通过添加尾随空白行来更改提供给 awk 的数据,则可以省去整个END {...}块,简化awk代码并删除重复:

( cat data; echo ) | awk ...

相关内容