Módulo 3: Tuberías, redirección y composición
6. El kit de texto: sort, uniq, cut, tr y sed
Descripción
Al terminar esta lección vas a poder ordenar líneas de texto por cualquier criterio, contar cuántas veces se repite cada valor distinto, quedarte solo con las columnas que te interesan de un archivo con formato irregular, traducir o borrar caracteres específicos, y hacer sustituciones de texto — todo encadenado en una sola tubería que responde, en segundos, preguntas sobre un archivo de decenas de miles de líneas.
Esta es la habilidad detrás de preguntas que aparecen todas las semanas en un trabajo real: ¿qué dirección IP golpeó más veces el servidor esta madrugada?, ¿cuántos clientes distintos aparecen en este export de ventas?, ¿cuántas líneas de este log de errores corresponden a cada código de estado? Sin este kit, esas preguntas se responden abriendo el archivo en un editor de texto y contando a ojo — algo que funciona con cien líneas y se vuelve imposible con cien mil.
Conexión con el módulo: en la lección anterior aprendiste el operador | para conectar la
salida de un comando con la entrada del siguiente, y lo probaste con piezas genéricas
(grep, wc, head, xargs). Ahora vas a conocer las piezas especializadas que hacen que
esas tuberías realmente analicen texto en lugar de solo filtrarlo o contarlo en bruto.
Una mesa de trabajo para líneas de texto
Ninguno de estos cinco comandos sabe qué es una "tabla" en el sentido de una hoja de cálculo. Lo único que ven es texto: líneas separadas por saltos de línea y, dentro de cada línea, caracteres sueltos. Pero si tú les dices dónde empieza y termina cada "columna" —casi siempre marcándola con un carácter que se repite, como una coma o un espacio—, ese texto plano empieza a comportarse igual que una planilla: se puede ordenar por una columna, contar cuántas filas repiten el mismo valor, quedarte con dos columnas y descartar el resto.
sort ordena líneas completas. uniq colapsa y cuenta las que son idénticas. cut recorta
columnas. tr traduce o borra caracteres, uno por uno. sed busca un patrón de texto y lo
reemplaza. Cada uno hace una sola cosa —la misma filosofía Unix que ya viste— y el valor real
aparece cuando los conectas con | en la secuencia correcta.
Ejemplo trabajado: el patrón universal de conteo
Crea este archivo de prueba, una versión corta de un log de accesos:
cat > access.log << 'EOF'
203.0.113.5 - - [21/Jul/2026:10:02:13 +0000] "GET /index.html HTTP/1.1" 200
198.51.100.23 - - [21/Jul/2026:10:02:15 +0000] "GET /about.html HTTP/1.1" 200
203.0.113.5 - - [21/Jul/2026:10:02:18 +0000] "GET /contact.html HTTP/1.1" 200
203.0.113.5 - - [21/Jul/2026:10:02:20 +0000] "GET /index.html HTTP/1.1" 200
198.51.100.23 - - [21/Jul/2026:10:02:22 +0000] "GET /login.html HTTP/1.1" 404
203.0.113.5 - - [21/Jul/2026:10:02:25 +0000] "GET /index.html HTTP/1.1" 200
192.0.2.44 - - [21/Jul/2026:10:02:30 +0000] "GET /pricing.html HTTP/1.1" 200
198.51.100.23 - - [21/Jul/2026:10:02:33 +0000] "GET /about.html HTTP/1.1" 200
EOF
La pregunta: ¿qué dirección IP hizo más peticiones? Construyendo la tubería por partes, como ya practicaste en la lección anterior:
cut -d' ' -f1 access.log
Qué esperar: la primera columna de cada línea, una IP por renglón, en el mismo orden en que aparecen en el archivo — todavía sin ordenar ni contar.
cut -d' ' -f1 access.log | sort
Qué esperar: las mismas ocho IPs, ahora agrupadas — todas las repeticiones de una misma IP quedan una al lado de la otra. Esto es exactamente lo que el siguiente eslabón necesita.
cut -d' ' -f1 access.log | sort | uniq -c
Qué esperar:
1 192.0.2.44
3 198.51.100.23
4 203.0.113.5
Cada línea única, precedida por cuántas veces apareció. Pero está ordenado alfabéticamente por IP, no por frecuencia — para eso falta un último eslabón:
cut -d' ' -f1 access.log | sort | uniq -c | sort -rn | head -3
Qué esperar:
4 203.0.113.5
3 198.51.100.23
1 192.0.2.44
Ahí está la respuesta: 203.0.113.5 hizo más peticiones que ninguna otra. En un archivo real
de cien mil líneas, head -3 es lo que te salva de tener que mirar cien mil resultados.
El patrón
sort | uniq -c | sort -rn | heades probablemente la tubería que más vas a reescribir en tu carrera: funciona para IPs, para códigos de error, para usuarios, para productos — para cualquier archivo donde la pregunta sea "¿cuáles valores se repiten más?".
sort: ordenar con criterio (-n, -r, -u, -k)
Por defecto, sort ordena línea por línea, carácter por carácter, en orden alfabético
(técnicamente, por valor de byte). Eso alcanza para nombres, pero falla de una forma muy
concreta con números: para sort sin -n, la cadena "10" va antes que "9", porque
compara el primer carácter (1 contra 9) y 1 es "menor" alfabéticamente, sin importar que
10 sea mayor que 9 como número.
cat > scores.txt << 'EOF'
9
10
2
EOF
sort scores.txt
Qué esperar:
10
2
9
Ese orden es correcto para sort, pero probablemente no es el que quieres. La opción -n le
dice que compare los valores numéricos, no los caracteres:
sort -n scores.txt
Qué esperar:
2
9
10
Las otras tres opciones que vas a usar seguido:
-rinvierte el orden (de mayor a menor, o de Z a A).-uelimina líneas duplicadas mientras ordena — es el equivalente desort | uniq, pero en un solo paso, cuando lo único que necesitas es la lista de valores distintos, sin el conteo.-k Nordena por la columna N en lugar de la línea completa. Necesita-tpara decirle cuál es el separador de columnas si no son espacios.
cat > products.csv << 'EOF'
mouse,electronics,25
desk,furniture,120
keyboard,electronics,45
chair,furniture,80
monitor,electronics,150
EOF
sort -t',' -k3 -n products.csv
Qué esperar:
mouse,electronics,25
keyboard,electronics,45
chair,furniture,80
desk,furniture,120
monitor,electronics,150
Ordenado por precio, de menor a mayor. Aquí usamos -k3 (no -k3,3) porque el precio es la
última columna: no hay nada después que se cuele en la comparación. Pero -k N sin un
segundo número extiende la clave de comparación desde el campo N hasta el final de la
línea, no solo ese campo — así que si quisieras ordenar por categoría (columna 2) ignorando
el precio como desempate, necesitarías -k2,2, para acotar la clave exactamente a esa
columna.
uniq y uniq -c: por qué exigen una entrada ya ordenada
uniq elimina líneas repetidas consecutivas y las cuenta con -c. La palabra clave es
consecutivas: uniq no compara cada línea contra todas las demás del archivo, solo contra la
línea inmediatamente anterior. Si dos líneas idénticas están separadas por otras distintas,
uniq no las detecta como duplicadas.
cat > visits.txt << 'EOF'
/home
/about
/home
EOF
uniq -c visits.txt
Qué esperar:
1 /home
1 /about
1 /home
Tres líneas, tres conteos de "1" — uniq no se dio cuenta de que /home aparece dos veces,
porque entre las dos apariciones había una línea distinta. Por eso el patrón siempre es
sort primero:
sort visits.txt | uniq -c
Qué esperar:
2 /home
1 /about
sort agrupó las dos líneas /home una al lado de la otra; recién ahí uniq -c pudo
contarlas juntas. Esta dependencia —uniq necesita que lo idéntico esté adyacente, y sort
es quien lo pone adyacente— es la razón por la que casi nunca vas a escribir uniq sin un
sort antes en la misma tubería.
cut -d -f: quedarte con columnas (y sus límites)
cut extrae columnas de cada línea. -d define el carácter delimitador y -f cuáles
columnas (campos) quieres, contando desde 1.
cut -d',' -f1 products.csv
Qué esperar:
mouse
desk
keyboard
chair
monitor
Puedes pedir varias columnas separadas por coma (-f1,3) o un rango (-f1-2).
El límite real de cut aparece con separadores irregulares — cuando el mismo campo puede
estar separado por una cantidad variable del delimitador, como pasa con la salida de ls -l,
que alinea columnas con un número de espacios que cambia según el ancho de cada valor:
ls -l /etc | head -3
Qué esperar (esto es un ejemplo ilustrativo: en tu propia máquina el total, los archivos, los tamaños y el espaciado van a ser distintos — lo que importa es la forma, no estos valores exactos):
total 1288
drwxr-xr-x 3 root wheel 96 Mar 3 09:15 apache2
-rw-r--r-- 1 root wheel 1928 Mar 3 09:15 afpovertcp.cfg
Si intentas cut -d' ' -f2 esperando "el número de enlaces", el resultado es una cadena
vacía o un valor incorrecto en algunas líneas: cut trata cada espacio individual como un
delimitador nuevo, así que dos o tres espacios seguidos —los que usa ls para alinear
columnas— generan varios campos vacíos antes de llegar al valor real, y cuántos campos vacíos
aparecen depende de cuántos espacios de relleno tenga esa línea en particular. El número de
campo que contiene el dato que buscas termina siendo distinto línea por línea.
Esto es distinto de cómo sort separa columnas por defecto (sin -t), que sí trata una
racha de espacios como un solo separador — es justamente la razón por la que cut necesita
un delimitador de un solo carácter consistente para funcionar bien, y por qué en la próxima
sección vas a ver cómo arreglar este caso puntual antes de pasarlo a cut.
tr: traducir y borrar caracteres
tr no trabaja con archivos como argumento — solo lee de la entrada estándar, igual que
viste con < en la lección de redirección — y traduce o elimina caracteres, uno por uno, no
patrones de texto completos (eso es trabajo de sed, más abajo).
Traducir un conjunto de caracteres por otro:
echo "Deploy Started" | tr 'a-z' 'A-Z'
Qué esperar:
DEPLOY STARTED
Borrar caracteres con -d — útil, por ejemplo, para limpiar los retornos de carro (\r)
que dejan los archivos creados en Windows y que en macOS o Linux aparecen como un ^M pegado
al final de cada línea:
tr -d '\r' < windows_file.txt > clean_file.txt
Squeeze con -s colapsa repeticiones consecutivas de un carácter en una sola aparición —
y esto es exactamente lo que resuelve el problema de ls -l que viste en la sección anterior:
ls -l /etc | tr -s ' ' | cut -d' ' -f2 | head -3
Qué esperar (siguiendo con el mismo ejemplo ilustrativo de /etc de arriba — en tu
sistema estos tres números van a ser otros, pero cada línea sí te va a dar de forma
consistente el número de enlaces, sin campos vacíos colados en el medio):
1288
3
1
tr -s ' ' colapsó cada racha de espacios repetidos en uno solo antes de que cut viera la
línea, así que ahora cada espacio individual sí corresponde a un límite de columna real, y
cut -d' ' -f2 obtiene el número de enlaces de forma consistente en cada línea.
sed 's/viejo/nuevo/g': sustituciones de texto
sed busca un patrón y lo reemplaza. La forma más común es s/patrón/reemplazo/, donde s
significa "substitute" y las barras separan las tres partes.
cat > pets.txt << 'EOF'
cat cat dog
dog cat cat
EOF
sed 's/cat/dog/' pets.txt
Qué esperar:
dog cat dog
dog dog cat
Sin la bandera g, sed reemplaza solo la primera coincidencia de cada línea — por eso
en la primera línea solo cambió el primer cat, y en la segunda solo el primero de los dos.
Con g ("global"), reemplaza todas las coincidencias de cada línea:
sed 's/cat/dog/g' pets.txt
Qué esperar:
dog dog dog
dog dog dog
Por defecto, sed imprime el resultado en pantalla y no toca el archivo original —
pets.txt sigue teniendo cat si lo revisas después de estos dos comandos.
La advertencia sobre -i. Para editar el archivo directamente, sed tiene la bandera
-i — pero se comporta distinto en Linux (GNU sed) y en macOS (BSD sed, el que trae el
sistema de fábrica):
# En Linux (GNU sed): funciona tal cual, sin respaldo
sed -i 's/cat/dog/g' pets.txt
# En macOS (BSD sed), el mismo comando falla
sed -i 's/cat/dog/g' pets.txt
Qué esperar en macOS: un error de sintaxis (algo como extra characters at the end of s command), no la sustitución que buscabas. La razón: BSD sed exige que -i siempre reciba
un argumento —el sufijo para el archivo de respaldo—, incluso cuando no quieres respaldo. Al
no dárselo explícitamente, toma 's/cat/dog/g' como si fuera ese sufijo y pets.txt como si
fuera el script a ejecutar, y ninguna de las dos interpretaciones es la que querías.
La forma correcta en macOS, pasando una cadena vacía como sufijo:
sed -i '' 's/cat/dog/g' pets.txt
Y la forma que funciona igual en las dos plataformas, si necesitas un script portable — deja
un respaldo con la extensión .bak:
sed -i.bak 's/cat/dog/g' pets.txt
Esta es la única incompatibilidad real que te vas a encontrar seguido entre las cinco
herramientas de esta lección: sort, uniq, cut y tr, con las opciones que viste aquí,
se comportan igual en macOS y en Linux.
Un límite honesto: dónde termina este kit
awk existe, y para trabajar por columnas es más potente que cut, tr y sed juntos —
puede hacer aritmética, condicionales y formatear salida en una sola línea. Queda
deliberadamente fuera de esta guía: el kit de esta lección resuelve la enorme mayoría de
tareas del día a día, y awk es, por sí solo, suficiente material para otro tema completo.
Cuando encuentres un problema que estas cinco herramientas no resuelven con comodidad, esa es
la señal de que te toca aprenderlo.
Errores comunes
1. Pensar que uniq deduplica todo el archivo, sin importar el orden (conceptual). Qué
pasa: alguien corre uniq -c archivo.txt esperando el conteo real de cada valor distinto, y
el resultado muestra casi todo con conteo 1, como si nada se repitiera. Por qué: uniq solo
compara cada línea contra la inmediatamente anterior — si las repeticiones no están
adyacentes, no las ve como duplicadas. Cómo detectarlo: compara wc -l archivo.txt contra
sort archivo.txt | uniq | wc -l; si el segundo número es mucho menor, había duplicados que
uniq solo no estaba viendo. Cómo corregirlo: siempre sort archivo.txt | uniq -c, nunca
uniq -c archivo.txt a secas, salvo que ya tengas la certeza de que el archivo viene
ordenado.
2. Usar sed -i igual en macOS que en Linux. Qué pasa: un script que funciona perfecto en
un servidor Linux falla en la laptop con macOS de alguien del equipo, con un error de sintaxis
que no tiene nada que ver con la sustitución en sí. Por qué: BSD sed (macOS) exige que -i
reciba un argumento explícito —el sufijo de respaldo—, mientras que GNU sed (Linux) lo trata
como opcional. Cómo detectarlo: el mensaje de error menciona algo como extra characters o
trata tu archivo de entrada como si fuera un script de sed. Cómo corregirlo: usa
sed -i '' 's/.../.../g' archivo en macOS, o adopta la forma portable sed -i.bak si el
script tiene que correr en las dos plataformas sin cambios.
3. Usar cut -d' ' sobre texto con espaciado irregular. Qué pasa: extraes lo que crees
que es la columna N y el resultado es inconsistente —vacío en algunas líneas, con el valor
equivocado en otras. Por qué: cut cuenta cada aparición individual del delimitador como un
límite de columna nuevo; si el número de espacios de relleno cambia de línea a línea (como en
ls -l), el número de campo que contiene tu dato cambia con él. Cómo detectarlo: compara el
resultado de cut contra lo que ves a simple vista en un par de líneas distintas del
archivo — si no coincide de forma consistente, el delimitador no es tan simple como parece.
Cómo corregirlo: colapsa los espacios repetidos primero con tr -s ' ' antes de pasarle el
resultado a cut.
Ejercicios
1. Tienes este archivo con páginas visitadas, en el orden en que se visitaron (no ordenado):
cat > pages.txt << 'EOF'
/home
/about
/home
/pricing
/about
/home
/contact
/about
EOF
Escribe una sola tubería que te diga, de más a menos visitada, cuántas veces se visitó cada página.
Ver solución
sort pages.txt | uniq -c | sort -rn
Qué esperar:
3 /home
3 /about
1 /pricing
1 /contact
Por qué funciona: sort agrupa las líneas idénticas para que queden adyacentes —
condición que uniq -c necesita para contarlas correctamente—, y el segundo sort -rn
reordena esos conteos de mayor a menor en vez de dejarlos ordenados alfabéticamente por
página.
2. Usando products.csv de la lección (mouse,electronics,25 / desk,furniture,120 /
keyboard,electronics,45 / chair,furniture,80 / monitor,electronics,150), obtén la lista
de precios ordenada de menor a mayor, sin las otras dos columnas.
Ver solución
cut -d',' -f3 products.csv | sort -n
Qué esperar:
25
45
80
120
150
Por qué funciona: cut -d',' -f3 se queda solo con la tercera columna de cada línea, y
sort -n la ordena por valor numérico — sin -n, "120" quedaría antes que "25" porque sort
compararía los caracteres, no las cantidades.
3. Recibes notes.txt, un archivo exportado desde Windows, y notas que cada línea termina
con un carácter ^M visible al abrirlo con cat -A notes.txt. Limpia el archivo en una copia
nueva llamada notes_clean.txt sin usar un editor de texto.
Ver solución
tr -d '\r' < notes.txt > notes_clean.txt
Por qué funciona: ese ^M es el carácter de retorno de carro (\r) que Windows agrega al
final de cada línea además del salto de línea habitual. tr -d '\r' lee la entrada estándar
(por eso el archivo se conecta con <, no como argumento) y borra ese carácter específico sin
tocar nada más, dejando el resultado en el archivo nuevo gracias a >.
4. Tienes app.conf con varias líneas que contienen la palabra TODO, y necesitas
cambiarlas todas por DONE, editando el archivo directamente pero conservando un respaldo,
con un comando que funcione igual en Linux y en macOS.
Ver solución
sed -i.bak 's/TODO/DONE/g' app.conf
Por qué funciona: -i.bak le dice a sed que edite app.conf en el lugar y que, antes
de hacerlo, guarde una copia del original como app.conf.bak — esa forma con el sufijo
pegado a -i es la única sintaxis de sed -i que GNU sed (Linux) y BSD sed (macOS) aceptan
exactamente igual, sin necesitar una versión distinta del comando por plataforma. s/TODO/ DONE/g sustituye todas las apariciones (g) de TODO por DONE en cada línea.
Resumen y siguiente paso
Ya tienes el kit completo para convertir texto plano en respuestas: ordenar con sort (y sus
variantes -n, -r, -u, -k), contar con uniq -c sobre una entrada ya ordenada, recortar
columnas con cut -d -f sabiendo dónde se rompe con espaciado irregular, traducir o borrar
caracteres con tr, y sustituir texto con sed 's/.../.../g' sin pisar el archivo original
por accidente en macOS. Y tienes el patrón que los une a todos: sort | uniq -c | sort -rn | head.
Hasta ahora, cada tubería que construiste asumió, en silencio, que cada comando en el camino
funcionó. Eso no siempre es cierto: un grep puede no encontrar nada, un sed -i puede
fallar por la razón que acabas de ver, un archivo puede no existir. Lo que viene es cómo saber
—con certeza, no por intuición— si un comando funcionó o falló, y cómo encadenar comandos que
reaccionen a ese resultado.
Antes de avanzar deberías poder:
- explicar, sin mirar esta lección, por qué
uniq -c archivo.txtsin unsortantes suele dar conteos incorrectos; - escribir de memoria la tubería
sort | uniq -c | sort -rn | headpara encontrar los valores más frecuentes de cualquier archivo de texto; - reconocer, al ver un error de
sed -ien macOS, que el problema es la sintaxis de la bandera y no la sustitución en sí.
Recursos
- sort invocation — GNU Coreutils Manual
— referencia oficial de todas las opciones de
sort, incluida la sintaxis completa de-kcon inicio y fin de clave. - uniq invocation — GNU Coreutils Manual
— documentación oficial de
uniq, con el resto de las opciones (-d,-u) que esta lección no cubrió. - cut(1) — Linux manual page — página de
manual completa de
cut, con el detalle exacto de cómo interpreta el delimitador. - tr(1) — Linux manual page — página de
manual de
tr, con la sintaxis completa de rangos de caracteres y las banderas-dy-s. - sed, a stream editor — GNU sed Manual
— manual oficial de GNU sed, con el comando
s///y todas sus banderas en detalle.