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 | head es 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:

  • -r invierte el orden (de mayor a menor, o de Z a A).
  • -u elimina líneas duplicadas mientras ordena — es el equivalente de sort | uniq, pero en un solo paso, cuando lo único que necesitas es la lista de valores distintos, sin el conteo.
  • -k N ordena por la columna N en lugar de la línea completa. Necesita -t para 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.txt sin un sort antes suele dar conteos incorrectos;
  • escribir de memoria la tubería sort | uniq -c | sort -rn | head para encontrar los valores más frecuentes de cualquier archivo de texto;
  • reconocer, al ver un error de sed -i en macOS, que el problema es la sintaxis de la bandera y no la sustitución en sí.

Recursos