Existe-t-il un moyen simple de compter les caractères des mots dans un fichier, depuis le terminal?


8

J'ai 100 millions de lignes dans mon fichier.

Chaque ligne n'a qu'une seule colonne.

par exemple

aaaaa
bb
cc
ddddddd
ee

Je voudrais lister le nombre de caractères

Comme ça

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

etc.

Existe-t-il un moyen simple de le faire dans le terminal?


Réponses:


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

Le premier awkfiltre affichera simplement la longueur de chaque ligne du fichier appelé file. Je suppose que ce fichier contient un mot par ligne.

Le sort -n(trier les lignes de la sortie de awknumériquement dans l'ordre croissant) et uniq -c(compter le nombre de fois où chaque ligne se produit consécutivement) créera alors la sortie suivante à partir de celle pour les données données:

   3 2
   1 5
   1 7

Ceci est ensuite analysé par le deuxième awkscript qui interprète chaque ligne comme "X nombre de lignes ayant Y caractères" et produit la sortie souhaitée.


La solution alternative consiste à tout faire awket à conserver le nombre de longueurs dans un tableau. C'est un compromis entre efficacité, lisibilité / facilité de compréhension (et donc maintenabilité) quelle solution est la "meilleure".

Solution alternative:

$ 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

Pas besoin de trier dans awk (les tableaux indexés numériquement sont triés par défaut) (plus rapide).
Isaac

@Arrow je sais. J'ai cette solution commentée dans ma réponse parce que Sundeep m'a battu avec quelques secondes. J'y fais également allusion avec mon dernier paragraphe.
Kusalananda

Je pense que le commentaire devrait être utile aux utilisateurs des solutions (non inclus dans votre réponse (ou Sundeep) :-)…). Sinon: incluez un commentaire dans le même sens dans votre réponse et je supprimerai volontiers mes commentaires. :-)
Isaac

10

Une autre façon de tout faire awkseul

$ 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()]++ utiliser la longueur de la ligne d'entrée comme clé pour enregistrer le nombre
  • END{for(k in words)print k " character words - " words[k]} une fois toutes les lignes traitées, imprimer le contenu du tableau au format souhaité


Comparaison des performances, les nombres sélectionnés sont les meilleurs des deux essais

$ 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

Si le fichier ne contient que des caractères 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

Je ne sais pas pourquoi le temps perln'a pas beaucoup changé, probablement l'encodage doit être réglé d'une autre manière


Je viens d' ajouter cela à ma propre solution. Je l'ai supprimé quand j'ai vu le vôtre. :-)
Kusalananda

ouais je discutais de supprimer le mien avant de revoir votre montage :)
Sundeep

Pas besoin de trier un tableau indexé numériquement . Il est toujours ordonné avec un indice croissant. (enfin, au moins en awk :-))
Isaac

lengthsans ()travaux parfaitement bien ici, il pourrait donc être redondant d'ajouter des accolades. J'utilise GNU awk, cependant.
Sergiy Kolodyazhnyy

2
@SergiyKolodyazhnyy yup, le manuel de gnu awk ditIn 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

5

Voici un perléquivalent (avec - optionnel - sort):

$ 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

Si les index des clés sont numériques: le tableau de clés doit-il être trié en Perl?
Isaac

1
@Arrow: Cette réponse utilise un hachage (c'est-à-dire un tableau associatif avec des clés de chaîne), et ceux-ci ont un ordre de clé non défini, alors oui. En fait, la réponse est légèrement boguée car elle trie les clés sous forme de chaînes, pas sous forme de nombres. L'ajout {$a<=>$b}après le sortpermettrait de résoudre ce problème. Alternativement, on pourrait utiliser un tableau normal avec des touches numériques et simplement sauter toutes les clés dont la valeur est zéro / non définie.
Ilmari Karonen

@IlmariKaronen Merci, mieux maintenant. Quelle différence les accolades font !!
Isaac

Il serait plus efficace d'utiliser un tableau au lieu d'un hachage. L'OP veut des millions de lignes, donc tout surcoût de vérification et de saut de zéros pendant l'impression est facilement compensé par une indexation moins chère.
Peter Cordes

5

Une alternative un appel à GNU awk, en utilisant 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

L'algorithme de base collecte simplement le nombre de caractères dans un tableau. La partie finale imprime les comptes collectés formatés avec printf.

Rapide, simple, un seul appel à awk.

Pour être précis: un peu plus de mémoire est utilisée pour conserver le tableau.
Mais aucun tri n'est appelé (les index des tableaux numériques sont définis pour être toujours parcourus triés vers le haut avec PROCINFO), et un seul programme externe:, awkau lieu de plusieurs.


1
for inpeut arriver à donner des index de tableau numérique dans l'ordre numérique au moins pour certaines valeurs ou dans certaines implémentations awk, mais ce n'est pas obligatoire, pas traditionnel et certainement pas universel. Cela arrive souvent pour de petits ensembles comme 2 ou 3 ou peut-être 4; essayez 10 ou 20 sur chaque awk auquel vous avez accès (sans PROCINFO ou WHINY_USERS dans gawk) et je parie que 50 $ au moins un cas n'est pas trié.
dave_thompson_085

Merci pour votre contribution. En utilisant ceci : je crois qu'il est trié maintenant. :-)
Isaac

1
@ind_str_asctrie sous forme de chaînes, ce qui ne sera correct pour les nombres que s'ils sont tous à un seul chiffre (comme dans votre exemple); utilisez @ind_num_ascsi (n'importe laquelle) les valeurs peuvent être 10 ou plus. Et bien que ce soit moins un problème maintenant qu'auparavant, cette fonctionnalité n'est que gawk 4.0 .
dave_thompson_085
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.