Como faço uniq considerar apenas o primeiro campo?

3

Estou usando o FreeBSD 3.2-RELEASE

Se eu tiver algum texto classificado, como este last output -

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

- existe uma maneira de obter apenas uniq -c para considerar o primeiro campo (talvez com -s )? Nesse caso, a saída deve ser esta:

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

Ou alguma outra maneira usando awk ?

    
por Da No 27.10.2015 / 22:35

2 respostas

1

Com o GNU uniq, que suporta a opção -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

Como apontado nos comentários, assume que o primeiro campo é sempre sete caracteres, o que está no seu exemplo, mas se não for na vida real, não acho que haja uma maneira de fazer isso com o uniq (mais se você não tiver o GNU uniq, mesmo -w não funcionará), então aqui está uma solução 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

Veja como isso funciona:

$ perl -ne

Execute perl, não imprima cada linha por padrão e use o próximo argumento como o script.

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

Divida a linha de entrada no material antes do primeiro cólon e depois do primeiro cólon, em $1 e $2 . split funcionaria aqui também.

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

O hash %x será usado para uniquificar as linhas e a matriz @x para mantê-las em ordem (você poderia usar apenas sort keys %x , mas isso pressupõe que sort de perl classificará da mesma forma que a entrada é classificada.) Portanto, se nunca vimos a "chave" atual (o material antes dos dois primeiros pontos), inicialize uma entrada de hash para a chave e pressione a tecla em @x . A entrada de hash para cada chave é uma matriz de dois elementos contendo a contagem e o primeiro valor visto após os dois pontos, para que a saída possa conter esse valor.

$x{$1}[0]++

Incrementar a contagem.

END{

Inicie um bloco que será executado depois que toda a entrada tiver sido lida.

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

Imprima a contagem, preenchida com espaços, um espaço, a "chave", dois-pontos e o material depois do cólon.

foreach @x}

Faça isso para cada chave vista, em ordem e termine o bloco END.

<data

Leia o arquivo chamado data no diretório atual para obter a entrada. Você também pode simplesmente canalizar para o perl se tiver algum outro comando ou pipeline produzindo os dados.

    
por 27.10.2015 / 23:30
0

Eu usaria awk . Filtre e conte o primeiro campo separado por dois pontos, quando ele muda ou atingimos EOF, imprime toda a linha salva anteriormente e conta:

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

O script awk pode ser expandido assim:

# 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]
    }
}

Se você puder alterar os dados fornecidos ao awk adicionando uma linha em branco à direita, poderá dispensar todo o bloco END {...} , simplificando o código awk e removendo a duplicação:

( cat data; echo ) | awk ...
    
por 28.10.2015 / 01:35