¿Hay una manera fácil de contar caracteres en palabras en el archivo, desde la terminal?

Respuestas:

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

El primer awkfiltro simplemente imprimirá la longitud de cada línea en el archivo llamado file. Supongo que este archivo contiene una palabra por línea.

El sort -n(ordenar las líneas de la salida de awknuméricamente en orden ascendente) y uniq -c(contar el número de veces que cada línea ocurre consecutivamente) creará la siguiente salida a partir de eso para los datos dados:

   3 2
   1 5
   1 7

Esto es analizado por el segundo awkscript que interpreta cada línea como "X número de líneas que tienen caracteres Y" y produce la salida deseada.


La solución alternativa es hacerlo todo awky mantener conteos de longitudes en una matriz. Es una compensación entre eficiencia, legibilidad / facilidad de comprensión (y por lo tanto mantenibilidad) qué solución es la "mejor".

Solución 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
Kusalananda
fuente
No es necesario ordenar en awk (las matrices indexadas numéricamente se ordenan de manera predeterminada) (más rápido).
Isaac
@ Flecha lo sé. Tengo esa solución comentada en mi respuesta porque Sundeep me ganó con unos segundos. También aludo a esto con mi último párrafo.
Kusalananda
Creo que el comentario debería ser útil para los usuarios de las soluciones (no incluidas en su respuesta (o la de Sundeep) :-) ...). De lo contrario: incluya un comentario con el mismo efecto en su respuesta y felizmente eliminaré mis comentarios. :-)
Isaac
10

Otra forma de hacerlo todo awksolo

$ 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 la longitud de la línea de entrada como clave para guardar el conteo
  • END{for(k in words)print k " character words - " words[k]} Después de procesar todas las líneas, imprima el contenido de la matriz en el formato deseado


Comparación de rendimiento, los números seleccionados son los mejores de dos carreras

$ 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 el archivo solo tiene 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

No estoy seguro de por qué el tiempo perlno cambió mucho, probablemente la codificación debe establecerse de otra manera

Sundeep
fuente
Yo simplemente añadí que a mi propia solución. Aunque lo borré cuando vi el tuyo. :-)
Kusalananda
Sí, estaba debatiendo eliminar la mía antes de volver a ver tu edición :)
Sundeep
No es necesario ordenar una matriz indexada numéricamente . Siempre se ordena con un índice creciente. (bueno, al menos en awk :-))
Isaac
lengthsin ()funciona perfectamente bien aquí, por lo que podría ser redundante agregar llaves. Sin embargo, estoy usando GNU awk.
Sergiy Kolodyazhnyy
2
@SergiyKolodyazhnyy sí, dice el manual de GNU AwkIn 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

Aquí hay un perlequivalente (con - opcional - ordenar):

$ 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
conductor de acero
fuente
Si los índices de claves son numéricos: ¿Se debe ordenar la matriz de claves en Perl?
Isaac
1
@Arrow: esta respuesta está usando un hash (es decir, una matriz asociativa con teclas de cadena), y esos tienen un orden de teclas indefinido, así que sí. De hecho, la respuesta es un poco defectuosa porque está ordenando las teclas como cadenas, no como números. Agregar {$a<=>$b}después de la sortsolucionaría eso. Alternativamente, uno podría usar una matriz normal con teclas numéricas y simplemente omitir cualquier tecla donde el valor sea cero / indefinido.
Ilmari Karonen
@IlmariKaronen Gracias, mejor ahora. ¡Qué diferencia hacen las llaves!
Isaac
Sería más eficiente usar una matriz en lugar de un hash. El OP quiere millones de líneas, por lo que cualquier sobrecarga de verificar y omitir ceros durante la impresión se compensa fácilmente con una indexación más barata.
Peter Cordes
5

Una alternativa una llamada a awk GNU, 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

El algoritmo central solo recopila los recuentos de caracteres en una matriz. La parte final imprime los recuentos recopilados formateados con printf.

Rápido, simple, una sola llamada a awk.

Para ser precisos: se utiliza algo más de memoria para mantener la matriz.
Pero no se llama ningún tipo de ordenación (los índices de matrices numéricas se configuran para que se recorran siempre ordenados hacia arriba con PROCINFO), y solo un programa externo: en awklugar de varios.

Isaac
fuente
1
for inpuede suceder que proporcione índices de matriz numérica en orden numérico al menos para algunos valores o en algunas implementaciones awk, pero eso no es obligatorio, no es tradicional y definitivamente no es universal. A menudo sucede para conjuntos pequeños como 2 o 3 o tal vez 4; pruebe 10 o 20 en cada awk al que tenga acceso (sin PROCINFO o WHINY_USERS en gawk) y apuesto a $ 50 al menos un caso no está ordenado.
dave_thompson_085
Gracias por tu contribución. Usando esto : creo que ahora está ordenado. :-)
Isaac
1
@ind_str_ascse ordena como cadenas, que serán correctas para los números solo si son todos de un solo dígito (como lo es su ejemplo); use @ind_num_ascif (any) los valores pueden ser 10 o más. Y aunque ahora es un problema menor de lo que solía ser, esta característica solo es gawk 4.0 .
dave_thompson_085