Contar palabras en la terminal parece el ejercicio más aburrido del mundo hasta que lo necesitas de verdad: comprobar si un artículo llega al mínimo que te han pedido, saber cuántas líneas tiene un registro antes de abrirlo, averiguar qué palabra repites hasta la náusea en todo lo que escribes.
El comando de siempre es wc, y en treinta segundos se aprende. Lo interesante empieza después, cuando lo enchufas a grep, sort y awk y de pronto puedes preguntarle cosas a un libro entero.
Hay un problema con casi todas las recetas que vas a encontrar por ahí: están escritas y probadas en inglés. En cuanto las lanzas contra un texto en español, parten «corazón» por la mitad, se inventan palabras que no existen y te dejan una tabla de frecuencias que parece correcta y no lo es. Este artículo enseña las dos cosas: los comandos, y dónde te van a mentir.
Todos los números de este artículo están medidos, no copiados. Para que puedas reproducirlos exactamente, todos salen del mismo fichero: el Quijote completo del Proyecto Gutenberg, que es de dominio público y pesa 2,2 MB.
curl -o quijote.txt https://www.gutenberg.org/cache/epub/2000/pg2000.txt
Si tus cifras no coinciden con las de aquí, no es cosa tuya: Gutenberg actualiza sus ficheros de vez en cuando.
wc, los cinco números que sabe dar
wc viene de word count, pero cuenta bastante más que palabras. Lanzado a secas escupe tres cifras y el nombre del fichero:
wc quijote.txt
38059 389656 2225803 quijote.txt
Son, por este orden, líneas, palabras y bytes. Casi 390.000 palabras y 2,2 millones de bytes.
Si solo quieres una, se la pides:
| Opción | Qué cuenta | En el Quijote |
-l | Líneas | 38.059 |
-w | Palabras | 389.656 |
-c | Bytes | 2.225.803 |
-m | Caracteres | 2.168.116 |
-L | Ancho de la línea más larga | 78 |
Un par de detalles que ahorran disgustos:
-wcuenta lo que hay entre espacios, no palabras del diccionario. «Don Quijote de la Mancha» son cinco. YIP-2es una sola.- El orden de las opciones no cambia el orden de la salida. Si escribes
wc -c -m ficheroesperando bytes y luego caracteres, te llevas una sorpresa:wcsiempre imprime en su orden fijo —líneas, palabras, caracteres, bytes, línea más larga— pongas los flags como los pongas. Es una fuente de errores estupenda dentro de un script.
printf "camión" | wc -c -m
6 7
Seis y siete, no siete y seis: primero los caracteres (-m) y después los bytes (-c). Y esa diferencia de uno es justo el tema del capítulo siguiente.
Bytes y caracteres no son lo mismo: 57.687 de diferencia
Aquí es donde el español se separa del inglés. Pide las dos cifras del Quijote:
wc -c quijote.txt # 2225803 bytes
wc -m quijote.txt # 2168116 caracteres
57.687 bytes de diferencia. En un texto en inglés esa resta daría cero o casi. Aquí no, porque en UTF-8 las letras que llevan tilde, la eñe y los signos de apertura no caben en un byte:
printf "año" | wc -c -m # 3 caracteres, 4 bytes
printf "corazón" | wc -c -m # 7 caracteres, 8 bytes
En todo el libro hay 50.712 caracteres que no son ASCII, y entre todos ocupan 108.399 bytes. Estos son los que más se repiten:
| Carácter | Veces | Bytes que ocupa |
| í | 12.464 | 2 |
| ó | 9.224 | 2 |
| á | 7.136 | 2 |
| é | 7.104 | 2 |
| — (raya de diálogo) | 6.925 | 3 |
| ñ | 4.272 | 2 |
| ú | 1.272 | 2 |
| ¿ | 959 | 2 |
| ¡ | 685 | 2 |
La mayoría pesan dos bytes, pero la raya de diálogo —esa que abre los diálogos del Quijote casi siete mil veces— pesa tres. Y hay casos peores: la bandera de España es un solo emoji para ti y ocho bytes para el disco.
printf "🇪🇸" | wc -c -m
2 8
Dos caracteres, porque un emoji de bandera son en realidad dos símbolos superpuestos.
-c. Si lo que te importa es cuánto texto hay —un límite de 280 caracteres, el titular de una ficha—, cuenta caracteres con -m. Confundirlas es la razón por la que un texto de 140 caracteres a veces no entra en un campo de 140.El carácter invisible que descuadra las cuentas
Pregúntale a wc por la línea más larga del libro y luego pregúntaselo a awk. No dicen lo mismo:
wc -L quijote.txt
# 78
awk '{ if (length($0) > m) { m = length($0); l = NR } } END { print l, m }' quijote.txt
# 945 79
Setenta y ocho contra setenta y nueve. Uno de los dos sobra, y el culpable no se ve:
sed -n '945p' quijote.txt | cat -A
GANDALÍN, ESCUDERO DE AMADÍS DE GAULA, A SANCHO PANZA, ESCUDERO DE DON QUIJOTE^M$
Ese ^M del final es un retorno de carro, el \r. El fichero viene con saltos de línea de Windows (CRLF) en vez de los de Unix (LF), así que cada línea arrastra un carácter de más. awk lo cuenta porque forma parte de la línea; wc -L no, porque mide el ancho en pantalla y el retorno de carro no ocupa nada.
Son 38.059 retornos de carro, uno por línea, y cada uno es un byte que no pinta nada:
tr -cd '\r' < quijote.txt | wc -c
# 38059
Se limpia de una pasada:
tr -d '\r' < quijote.txt > quijote-limpio.txt
Y a partir de ahí las dos herramientas se ponen de acuerdo en 78.
.txt que te ha mandado un cliente, un fichero de un repositorio con la configuración de saltos de línea mal puesta— trae lo mismo. Si un recuento te sale sistemáticamente uno más de lo que esperas, empieza mirando aquí.Quítale el nombre del fichero a la respuesta
Esto parece una tontería y se convierte en un fastidio en cuanto metes wc dentro de un script:
wc -l quijote.txt
# 38059 quijote.txt
wc -l < quijote.txt
# 38059
La diferencia es quién abre el fichero. En el primer caso se lo pasas como argumento, wc sabe cómo se llama y lo imprime. En el segundo se lo das ya abierto por la entrada estándar: no tiene ni idea de dónde viene, así que solo puede darte el número.
Cuando la cifra va a parar a una variable, la segunda forma es la que quieres:
lineas=$(wc -l < quijote.txt)
echo "El fichero tiene $lineas líneas"
Con la primera te habrías llevado 38059 quijote.txt dentro de la variable, y la siguiente comparación numérica habría reventado.
Con varios ficheros a la vez añade un total:
wc -l /etc/hosts /etc/passwd
7 /etc/hosts
42 /etc/passwd
49 total
Ojo con ese total si luego procesas la salida: es una línea más que no es ningún fichero.
Y para el largo de un texto que ya tienes en una variable no hace falta wc ni tubería ninguna, lo hace el propio shell:
palabra="camión"
echo "${#palabra}" # 6
Contar apariciones no es contar líneas
Cuántas veces sale Sancho en el libro. Parece una pregunta con una sola respuesta y tiene dos:
grep -c "Sancho" quijote.txt
# 2149
grep -o "Sancho" quijote.txt | wc -l
# 2173
Veinticuatro de diferencia, y ninguno de los dos está mal: cuentan cosas distintas. grep -c cuenta líneas que contienen la palabra, y una línea con dos Sanchos suma uno. grep -o imprime cada aparición en su propia línea, así que la misma línea suma dos.
Se ve mejor en pequeño:
printf 'Sancho y Sancho\notra linea\n' > mini.txt
grep -c "Sancho" mini.txt # 1
grep -o "Sancho" mini.txt | wc -l # 2
La regla práctica: si la pregunta es «¿en cuántos sitios aparece?», -c. Si es «¿cuántas veces se dice?», -o y a contar. En un registro de servidor esa diferencia es la que hay entre «cuántas peticiones fallaron» y «cuántas veces apareció el error», que rara vez es el mismo número.
Las palabras más repetidas (y por qué la receta inglesa miente)
Esta es la tubería que circula por medio internet para sacar el ranking de palabras de un texto:
tr -sc '[:alpha:]' '\n' < quijote.txt | tr '[:upper:]' '[:lower:]' \
| sort | uniq -c | sort -rn | head
Va por partes: tr -sc '[:alpha:]' '\n' cambia todo lo que no es una letra por un salto de línea, de forma que queda una palabra por renglón; el segundo tr pasa a minúsculas; sort las agrupa; uniq -c cuenta cada grupo; y sort -rn ordena de mayor a menor.
En inglés funciona perfectamente. En español devuelve esto:
| Puesto | Con tr (byte a byte) | Con grep (respeta el idioma) |
| 1 | que — 20.769 | que — 20.769 |
| 2 | de — 18.411 | de — 18.411 |
| 3 | y — 18.280 | y — 18.272 |
| 4 | a — 15.700 | la — 10.492 |
| 5 | la — 10.528 | a — 9.934 |
| 6 | en — 8.295 | en — 8.285 |
| 7 | el — 8.271 | el — 8.265 |
| 8 | se — 7.035 | no — 6.356 |
| 9 | no — 6.397 | los — 4.769 |
| 10 | los — 4.776 | se — 4.752 |
| 11 | con — 4.275 | con — 4.275 |
| 12 | n — 4.195 ⚠️ | por — 3.945 |
Fíjate en la columna de la izquierda. Ahí abajo hay una «n» suelta que aparece 4.195 veces, y «n» no es una palabra en español. Tampoco cuadran «a» ni «se».
El motivo es que tr trabaja byte a byte y no entiende UTF-8. Para él, los dos bytes de una «ó» no son letras, así que los trata como separadores y parte la palabra en dos:
printf 'corazón\n' | tr -sc '[:alpha:]' '\n'
coraz
n
De «corazón» salen «coraz» y «n». Y como el español está lleno de palabras acabadas en -ón, -án y -én —razón, también, capitán, corazón—, esa «n» huérfana se repite miles de veces. Con «año» es aún peor: la eñe desaparece del todo y quedan «a» y «o».
La solución es cambiar la herramienta que corta, no la que cuenta. grep sí respeta el idioma configurado en el sistema:
grep -oE '[[:alpha:]]+' quijote.txt | tr '[:upper:]' '[:lower:]' \
| sort | uniq -c | sort -rn | head
Con esa versión la «n» fantasma desaparece —comprobado: cero apariciones— y «señor» pasa de invisible a estar entre las palabras más usadas del libro.
La segunda trampa: tr tampoco sabe poner minúsculas
Ya hemos arreglado el corte de palabras, pero en la tubería de arriba queda un tr más, el que pasa a minúsculas. Y falla exactamente por lo mismo:
printf 'ÁNGEL ESPAÑA Ñ\n' | tr '[:upper:]' '[:lower:]'
Ángel espaÑa Ñ
Ha bajado las letras normales y ha dejado la «Á» y la «Ñ» tal cual. El resultado no es solo feo: en la tabla de frecuencias, «Ángel» y «ángel» cuentan como dos palabras distintas. En el Quijote pasa 178 veces, repartidas en 40 palabras distintas: «Éste» treinta y siete veces, «Él» veintinueve, «Álvaro» veinte.
Cualquiera de estas dos sí lo hace bien:
sed 's/.*/\L&/' # GNU sed
awk '{ print tolower($0) }' # gawk
printf 'ÁNGEL ESPAÑA Ñ\n' | sed 's/.*/\L&/'
# ángel españa ñ
Con las dos piezas cambiadas, la tubería que sí funciona en español queda así:
grep -oE '[[:alpha:]]+' quijote.txt \
| sed 's/.*/\L&/' \
| sort | uniq -c | sort -rn | head
tr es de bytes; grep, sed y awk son de caracteres. Para tocar texto en cualquier idioma que no sea inglés, tr solo sirve para trabajos que no dependan del alfabeto —borrar retornos de carro, por ejemplo—.Frecuencia de letras y de pares de letras
Cambiando el + por nada en la expresión, cada letra sale por su cuenta y tienes la frecuencia del alfabeto:
grep -oE '[[:alpha:]]' quijote.txt | sed 's/.*/\L&/' \
| sort | uniq -c | sort -rn | head
| Letra | Veces |
| e | 225.257 |
| a | 195.635 |
| o | 155.785 |
| s | 127.289 |
| n | 110.260 |
| r | 102.719 |
| l | 90.168 |
| d | 88.476 |
| u | 79.300 |
| i | 79.194 |
E, A, O, S, N, R. Es el orden clásico del español y explica por qué la Ñ, que tiene un lugar de honor en el idioma, aparece cuatro mil veces frente a las doscientas veinticinco mil de la E.
Los pares de letras (bigramas) hacen falta un poco más de maña, porque hay que recorrer cada palabra deslizando una ventana de dos:
grep -oE '[[:alpha:]]+' quijote.txt | sed 's/.*/\L&/' \
| awk '{ for (i = 1; i < length($0); i++) print substr($0, i, 2) }' \
| sort | uniq -c | sort -rn | head
| Par | Veces |
| ue | 37.207 |
| de | 34.487 |
| en | 34.102 |
| es | 32.903 |
| qu | 32.725 |
| er | 27.606 |
| os | 25.773 |
| la | 23.321 |
| do | 21.821 |
| an | 21.684 |
El podio dice mucho del idioma: «ue» y «qu» están altísimos por culpa de una sola palabra, «que», que es también la más repetida del libro con 20.769 apariciones.
Y tarda menos de un segundo en recorrer dos millones de caracteres, que para un awk de una línea no está nada mal.
Palabras de cinco letras, o cómo hacer trampas al Wordle
awk filtra por longitud, así que sacar las palabras de exactamente cinco letras es añadir una condición:
grep -oE '[[:alpha:]]+' quijote.txt | sed 's/.*/\L&/' \
| awk 'length($0) == 5' \
| sort | uniq -c | sort -rn | head
| Palabra | Veces |
| señor | 1.065 |
| había | 1.037 |
| todos | 818 |
| donde | 722 |
| quien | 621 |
| decir | 578 |
| hacer | 535 |
| aquel | 487 |
| sobre | 456 |
| tanto | 421 |
Como diccionario de Wordle el Quijote es un poco tramposo —«señor» y «había» salen tantas veces por el tipo de libro que es—, pero la técnica sirve para cualquier corpus. Cambia el 5 por el número que quieras y tienes las palabras de esa longitud ordenadas por uso real, que es bastante mejor guía que una lista alfabética.
Si el filtro lo quieres por otra cosa, awk acepta lo que se te ocurra:
awk 'length($0) >= 12' # palabras largas
awk '/^des/' # las que empiezan por des-
awk 'length($0) == 5 && /a/' # de cinco letras y con una a
LC_ALL=C: cuatro veces más rápido y te destroza el español
En cuanto buscas cómo acelerar estas tuberías aparece siempre el mismo consejo: ponle LC_ALL=C delante. Y es verdad que corre. Sobre el Quijote, la misma tubería:
| Cómo se lanza | Tiempo | ¿Cuenta bien el español? |
| Con el idioma del sistema | 358 ms | Sí |
Todo con LC_ALL=C | 83 ms | No — pierde tildes y eñes |
Cortar con idioma + ordenar en C | 156 ms | Sí |
Más de cuatro veces más rápido. Lo que casi nadie añade es lo que estás pagando: LC_ALL=C le dice al sistema que se olvide del idioma y trate el texto como bytes sueltos, que es exactamente el problema que acabamos de arreglar dos capítulos más arriba.
printf 'corazón\n' | LC_ALL=C grep -oE '[[:alpha:]]+'
coraz
n
Vuelve la «n» fantasma, otras 4.181 veces. Y «señor», que aparece 1.065 veces en el libro, pasa a aparecer cero.
La forma correcta de quedarse con lo bueno es separar los dos trabajos: cortar las palabras respetando el idioma, y ordenarlas sin él. A sort y a uniq les da igual el alfabeto mientras agrupen lo idéntico:
grep -oE '[[:alpha:]]+' quijote.txt \
| sed 's/.*/\L&/' \
| LC_ALL=C sort | LC_ALL=C uniq -c | LC_ALL=C sort -rn \
| head
0,156 segundos y los resultados intactos: «que» 20.769, «de» 18.411, «y» 18.272. Más del doble de rápido que la versión ingenua y sin romper nada.
Contar ficheros sin que te engañen
El clásico para saber cuántos ficheros hay en una carpeta:
ls /etc | wc -l
# 247
Y está mal por dos motivos. El primero es que cuenta también las carpetas; los ficheros de verdad son la mitad:
find /etc -maxdepth 1 -type f | wc -l
# 121
El segundo es más traicionero: wc -l cuenta saltos de línea, así que un fichero cuyo nombre contenga un salto de línea cuenta por dos. Suena imposible hasta que alguien descomprime un ZIP raro en tu servidor. La versión a prueba de nombres imposibles no cuenta líneas, cuenta puntos:
find /etc -maxdepth 1 -type f -printf '.' | wc -c
# 121
find imprime un punto por cada fichero encontrado, sin saltos de línea de por medio, y wc -c cuenta los puntos. El nombre ya no participa.
Quitando el -maxdepth 1 cuentas el árbol entero, y con -name filtras por extensión:
find . -type f -name '*.md' -printf '.' | wc -c
Contar las palabras de lo que escribes tú
Este es el uso que más veces acabas dándole. Un artículo en Markdown, contado en bruto:
cat content/*.md | wc -w
# 1928
Ese número está inflado, porque ahí dentro van las cabeceras del fichero y las etiquetas de la plantilla, que no son texto que nadie vaya a leer. Quitándolas:
cat content/*.md \
| sed '/^---$/,/^---$/d' \
| sed -E 's/\{\{[<%][^}]*[>%]\}\}//g' \
| grep -oE '[[:alpha:]]+' | wc -l
# 1747
181 palabras de diferencia, casi un 10 %. Si escribes para alguien que paga por palabra o exige un mínimo, la distinción no es académica.
Y para ver el reparto por capítulos, wc acepta comodines:
wc -w content/*.md
Los comandos que más usas salen del historial. En Bash el historial es un fichero de texto plano, así que basta con contarlo:
cut -d' ' -f1 ~/.bash_history | sort | uniq -c | sort -rn | head
En Zsh las líneas llevan una marca de tiempo delante que hay que quitar antes:
sed 's/^: [0-9]*:[0-9]*;//' ~/.zsh_history | cut -d' ' -f1 \
| sort | uniq -c | sort -rn | head
Y en Fish el historial es un YAML, con el comando detrás de - cmd::
grep '^- cmd: ' ~/.local/share/fish/fish_history \
| awk '{ print $3 }' | sort | uniq -c | sort -rn | head
Chuleta
Todo lo anterior en una tabla, para cuando vuelvas dentro de seis meses sin acordarte de nada:
| Lo que quieres | El comando |
| Líneas de un fichero | wc -l fichero |
| Solo el número sin el nombre | wc -l < fichero |
| Palabras | wc -w fichero |
| Caracteres (tildes incluidas) | wc -m fichero |
| Bytes en disco | wc -c fichero |
| Largo de una variable | ${#variable} |
| Líneas que contienen algo | grep -c patrón fichero |
| Veces que aparece algo | grep -o patrón fichero | wc -l |
| Ficheros de una carpeta | find . -maxdepth 1 -type f -printf '.' | wc -c |
| Palabras más repetidas | grep -oE '[[:alpha:]]+' f | sed 's/.*/\L&/' | sort | uniq -c | sort -rn |
| Quitar los saltos de línea de Windows | tr -d '\r' < entrada > salida |
Y las tres reglas que evitan el 90 % de los sustos:
trno entiende acentos. Para cortar palabras usagrep -oE '[[:alpha:]]+'; para pasar a minúsculas,sed 's/.*/\L&/'.LC_ALL=Ces para ordenar, no para cortar. Ponlo ensortyuniq, nunca en elgrepque separa las palabras.- Si un número no te cuadra por poco, busca lo invisible: un retorno de carro, un
totalde más al final de la salida o el nombre del fichero colado en la respuesta.











