Existe uma maneira fácil de contar caracteres em palavras no arquivo, a partir do terminal?


8

Eu tenho 100 milhões de linhas no meu arquivo.

Cada linha possui apenas uma coluna.

por exemplo

aaaaa
bb
cc
ddddddd
ee

Eu gostaria de listar a contagem de caracteres

Como isso

2 character words - 3
5 character words - 1
7 character words - 1

etc.

Existe alguma maneira fácil de fazer isso no terminal?


Respostas:


20
$ awk '{ print length }' file | sort -n | uniq -c | awk '{ printf("%d character words: %d\n", $2, $1) }'
2 character words: 3
5 character words: 1
7 character words: 1

O primeiro awkfiltro apenas imprimirá o comprimento de cada linha no arquivo chamado file. Estou assumindo que este arquivo contém uma palavra por linha.

O sort -n(classificar as linhas da saída de awknumericamente em ordem crescente) e uniq -c(contar o número de vezes que cada linha ocorre consecutivamente) criará a seguinte saída para os dados fornecidos:

   3 2
   1 5
   1 7

Isso é analisado pelo segundo awkscript que interpreta cada linha como "número X de linhas com caracteres Y" e produz a saída desejada.


A solução alternativa é fazer tudo isso awke manter contagens de comprimentos em uma matriz. É uma troca entre eficiência, legibilidade / facilidade de entendimento (e, portanto, capacidade de manutenção) de qual solução é a "melhor".

Solução alternativa:

$ awk '{ len[length]++ } END { for (i in len) printf("%d character words: %d\n", i, len[i]) }' file
2 character words: 3
5 character words: 1
7 character words: 1

Não há necessidade de classificar no awk (matrizes indexadas numericamente são classificadas por padrão) (mais rápido).
Isaac

@ Arrow eu sei. Eu tenho essa solução comentada na minha resposta porque a Sundeep me venceu com alguns segundos. Também aludo isso com o meu último parágrafo.
Kusalananda

Eu acredito que o comentário deve ser útil para os usuários das soluções (não incluídas na sua resposta (ou na Sundeep) :-)…). Caso contrário: inclua um comentário com o mesmo efeito em sua resposta e, felizmente, removerei meus comentários. :-)
Isaac

10

Outra maneira de fazer tudo awksozinho

$ awk '{words[length()]++} END{for(k in words)print k " character words - " words[k]}' ip.txt 
2 character words - 3
5 character words - 1
7 character words - 1
  • words[length()]++ use o comprimento da linha de entrada como chave para salvar a contagem
  • END{for(k in words)print k " character words - " words[k]} depois que todas as linhas forem processadas, imprima o conteúdo da matriz no formato desejado


Comparação de desempenho, os números selecionados são os melhores de duas execuções

$ wc words.txt
 71813  71813 655873 words.txt
$ perl -0777 -ne 'print $_ x 1000' words.txt > long_file.txt
$ du -h --apparent-size long_file.txt
626M    long_file.txt

$ time awk '{words[length()]++} END{for(k in words)print k " character words - " words[k]}' long_file.txt > t1

real    0m20.632s
user    0m20.464s
sys     0m0.108s

$ time perl -lne '$h{length($_)}++ }{ for $n (sort keys %h) {print "$n character words - $h{$n}"}' long_file.txt > t2

real    0m19.749s
user    0m19.640s
sys     0m0.108s

$ time awk '{ print length }' long_file.txt | sort -n | uniq -c | awk '{ printf("%d character words - %d\n", $2, $1) }' > t3

real    1m23.294s
user    1m24.952s
sys     0m1.980s

$ diff -s <(sort t1) <(sort t2)
Files /dev/fd/63 and /dev/fd/62 are identical
$ diff -s <(sort t1) <(sort t3)
Files /dev/fd/63 and /dev/fd/62 are identical

Se o arquivo tiver apenas caracteres ASCII,

$ time LC_ALL=C awk '{words[length()]++} END{for(k in words)print k " character words - " words[k]}' long_file.txt > t1

real    0m15.651s
user    0m15.496s
sys     0m0.120s

Não sei por que o tempo para perlnão mudou muito, provavelmente a codificação deve ser definida de outra maneira


Eu apenas acrescentou que a minha própria solução. Excluído quando vi o seu, no entanto. :-)
Kusalananda

sim, eu estava debatendo para apagar o meu antes viu sua edição novamente :)
Sundeep

Não há necessidade de classificar uma matriz numerada indexada . É sempre solicitado com um índice crescente. (Bem, pelo menos em awk :-))
Isaac

lengthsem ()funciona perfeitamente bem aqui, por isso pode ser redundante adicionar chaves. Estou usando o GNU awk, no entanto.
Sergiy Kolodyazhnyy

2
@SergiyKolodyazhnyy yup, gnu awk manual saysIn older versions of awk, the length() function could be called without any parentheses. Doing so is considered poor practice, although the 2008 POSIX standard explicitly allows it, to support historical practice. For programs to be maximally portable, always supply the parentheses
Sundeep 9/17

5

Aqui está um perlequivalente (com - opcional - classificação):

$ perl -lne '
    $h{length($_)}++ }{ for $n (sort keys %h) {print "$n character words - $h{$n}"}
' file
2 character words - 3
5 character words - 1
7 character words - 1

Se os índices de chaves forem numéricos: A matriz de chaves precisa ser classificada em Perl?
Isaac

1
@ Seta: Esta resposta está usando um hash (ou seja, matriz associativa com chaves de seqüência de caracteres), e essas têm ordem de chave indefinida, então sim. De fato, a resposta é um pouco problemática, porque classifica as teclas como strings, não como números. Adicionar {$a<=>$b}depois de sortcorrigir isso. Como alternativa, pode-se usar uma matriz normal com teclas numéricas e pular todas as chaves em que o valor é zero / indefinido.
Ilmari Karonen

@IlmariKaronen Obrigado, melhor agora. Que diferença os aparelhos encaracolados fazem !!
Isaac

Seria mais eficiente usar uma matriz em vez de um hash. O OP quer milhões de linhas, portanto, qualquer sobrecarga de verificação e pular zeros durante a impressão é facilmente compensada pela indexação mais barata.
Peter Cordes

5

Uma alternativa uma chamada para GNU awk, usando printf :

$ awk 'BEGIN { PROCINFO["sorted_in"] = "@ind_str_asc"}
       {c[length($0)]++}
       END{
           for(i in c){printf("%s character words - %s\n",i,c[i])}
          }' infile
2 character words - 3
5 character words - 1
7 character words - 1

O algoritmo principal apenas coleta as contagens de caracteres em uma matriz. A parte final imprime as contagens coletadas formatadas com printf.

Rápido, simples, uma única chamada para o awk.

Para ser preciso: mais memória é usada para manter a matriz.
Mas nenhuma classificação é chamada (os índices de matrizes numéricos são configurados para serem sempre percorridos ordenados para cima com PROCINFO) e apenas um programa externo: em awkvez de vários.


1
for inpode fornecer índices de matriz numérica em ordem numérica, pelo menos para alguns valores ou em algumas implementações do awk, mas isso não é necessário, nem tradicional e, definitivamente, não universal. Isso acontece frequentemente em pequenos conjuntos como 2 ou 3 ou talvez 4; tente 10 ou 20 em todos os awk aos quais você tem acesso (sem PROCINFO ou WHINY_USERS em gawk) e aposto que US $ 50 pelo menos um caso não está classificado.
David_thompson_085

Obrigado pela sua contribuição. Usando isso : acredito que está classificado agora. :-)
Isaac

1
@ind_str_ascclassifica como seqüências de caracteres, que só serão corretas para números se forem todos de um dígito (como é o seu exemplo); use @ind_num_ascse (algum) valor puder ser 10 ou mais. E, embora agora seja menos problemático do que costumava ser, esse recurso está disponível apenas com o máximo de 4.0 .
Dave_thompson_085
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.