Redirecciones y tuberías en Linux — ejercicios con intentos fallidos, rankings que se parten y un 2>&1 en el sitio equivocado (y un grep que contaba cero errores con la pantalla llena de ellos)

Una tarde, compilando la práctica de C, quise saber cuántos errores me daba gcc sin tener que contarlos con el dedo. Escribí gcc programa.c | grep -c error y la terminal me respondió con la pantalla llena de errores… y, al final, un 0. Cero errores, decía grep, mientras veía quince. Tardé un rato en entenderlo: gcc escribe sus errores por stderr, y por la tubería solo viaja stdout. grep no recibió nada y contó, con toda la razón, cero. Con 2>&1 delante de la barra salió el número de verdad. Estos ejercicios están llenos de trampas de ese tipo, así que antes de mirar la solución, predice.

Son los ejercicios de redirecciones y tuberías (si no has hecho la práctica, hazla antes). Trabajamos otra vez con accesos.log, el registro del aula 1.3: 16 líneas con fecha, hora, ordenador, usuario, evento (LOGIN/LOGOUT) y resultado (OK/FAIL).

Cómo usar este artículo

  1. Crea una carpeta de pruebas con el accesos.log de la práctica (las 16 líneas están al principio de la práctica).
  2. Lee el enunciado de cada nivel y la salida esperada, y escribe tus órdenes sin mirar la solución. Si te atascas, lee la pista.
  3. En «¿Qué falla aquí?», predice qué saldrá antes de leer la explicación. Es donde más se aprende.
  4. Compara con las soluciones y lee el porqué de cada decisión. Al final tienes el chuletario para repasar en 30 segundos.

Nivel 1 (básico): los intentos fallidos

Guarda en fallos.txt las líneas de accesos.log que son intentos de entrada fallidos y muestra su contenido. Después escribe solo el número de fallos (sin el nombre del fichero detrás).

Salida esperada (el contenido de fallos.txt y, después, el número):

2026-10-19 08:05 pc02 laura LOGIN FAIL
2026-10-19 08:30 pc04 ana LOGIN FAIL
2026-10-19 08:31 pc04 ana LOGIN FAIL
2026-10-19 10:02 pc01 dani LOGIN FAIL
2026-10-19 10:20 pc02 laura LOGIN FAIL
5

grep filtra y > guarda. Para que wc -l no escriba el nombre, no le des el nombre: dale los datos por stdin.

Nivel 2 (intermedio): quién falla más

Saca cuántos intentos fallidos tiene cada persona, de más a menos, y haz que se vean en pantalla y se guarden a la vez en fallos-por-usuario.txt, con una sola tubería. Después comprueba cuántas líneas tiene ese fichero.

Salida esperada:

      2 laura
      2 ana
      1 dani
3 fallos-por-usuario.txt

El patrón de contar es «sacar la columna → ordenar → contar → ordenar por número». Para ver y guardar a la vez hay una orden con forma de T.

Nivel 3 (desafío): el uso de cada ordenador

Crea uso.txt con una cabecera de dos líneas (Uso de los ordenadores del aula 1.3 y Generado por <tu usuario>, con el usuario puesto por bash, no escrito a mano) y debajo cuántos registros tiene cada ordenador. El registro del martes, martes.log, debería leerse también, pero todavía no existe: su error no debe salir en pantalla. Muestra uso.txt y, al final, solo la línea del ordenador más usado.

Salida esperada:

Uso de los ordenadores del aula 1.3
Generado por sergio
      4 pc01
      5 pc02
      3 pc03
      4 pc04
      5 pc02

(En tu ordenador saldrá tu usuario en lugar de sergio.)

La cabecera, con un here-document (<< EOF, sin comillas para que $USER se sustituya). Los datos, con >>. Para buscar el máximo, sáltate las dos líneas de cabecera con tail -n +3.

¿Qué falla aquí?

Tres fragmentos que parecen correctos. Para cada uno: lee la orden, predice la salida y solo entonces sigue leyendo.

Fragmento 1: contar los fallos de cada persona

# ¿Qué falla aquí? 1: contar los fallos de cada persona
grep FAIL accesos.log | cut -d' ' -f4 | uniq -c

¿Qué crees que sale? Esto es lo que salió de verdad:

$ grep FAIL accesos.log | cut -d' ' -f4 | uniq -c
      1 laura
      2 ana
      1 dani
      1 laura

laura aparece dos veces, con un 1 cada vez. No es un fallo de uniq: uniq compara cada línea solo con la anterior. Los usuarios de los fallos, en el orden del registro, son laura, ana, ana, dani, laura: las dos ana están pegadas y se juntan, pero las dos laura no. Fíjate en el dibujo:

uniq solo junta repetidos seguidosA la izquierda, los usuarios de los cinco fallos en el orden del registro: laura, ana, ana, dani, laura. uniq -c forma cuatro grupos y laura sale dos veces con un 1. A la derecha, tras sort quedan ana, ana, dani, laura, laura: tres grupos y laura con un 2.Sin sortgrupos de uniqlaura1 lauraanaana2 anadani1 danilaura1 lauraCon sortgrupos de uniqanaana2 anadani1 danilauralaura2 laura
uniq compara cada línea solo con la anterior: si los iguales no están pegados, los cuenta por separado.

Usar uniq -c sin un sort delante. Con datos pequeños o ya agrupados parece funcionar, y el día que no lo están te da recuentos partidos sin avisar.

El arreglo es meter sort antes:

# Arreglo: sort antes de uniq, para que los iguales queden juntos
grep FAIL accesos.log | cut -d' ' -f4 | sort | uniq -c

Y ahora sí:

$ grep FAIL accesos.log | cut -d' ' -f4 | sort | uniq -c
      2 ana
      1 dani
      2 laura

2 + 1 + 2 = 5 fallos, como en el registro.

Si solo quieres la lista de valores distintos (sin contar), sort -u hace sort | uniq de una vez.

Fragmento 2: guardarlo todo en todo.txt

Usamos el revisar.sh de la práctica: escribe tres líneas correctas por stdout y dos avisos por stderr.

#!/bin/bash
# revisar.sh: repasa los cuatro ordenadores del aula (simulado con echo)
echo "pc01: correcto"
echo "pc02: correcto"
echo "AVISO pc03: disco casi lleno" >&2   # >&2: este mensaje sale por stderr
echo "pc04: correcto"
echo "AVISO pc04: sin red" >&2

Queremos guardar las cinco líneas en todo.txt:

# ¿Qué falla aquí? 2: guardar en todo.txt lo correcto y los avisos
bash revisar.sh 2>&1 > todo.txt
cat todo.txt

¿Qué sale en pantalla y qué hay en el fichero?

$ bash revisar.sh 2>&1 > todo.txt
AVISO pc03: disco casi lleno
AVISO pc04: sin red
$ cat todo.txt
pc01: correcto
pc02: correcto
pc04: correcto

Los avisos salen en pantalla y todo.txt solo tiene las tres líneas correctas. Las redirecciones se procesan de izquierda a derecha: 2>&1 hace que el 2 apunte a donde apunta el 1 en ese momento, que todavía es la pantalla. Después > todo.txt mueve el 1 al fichero, pero el 2 ya tenía su destino y no lo sigue.

PasoFlujo 1Flujo 2
al empezarpantallapantalla
2>&1pantallapantalla (copia del 1)
> todo.txttodo.txtpantalla

Leer 2>&1 como «el 2 va siempre con el 1». Es una copia del destino actual del 1, no un enlace permanente.

El arreglo es cambiar el orden:

# Arreglo: primero > todo.txt y después 2>&1
bash revisar.sh > todo.txt 2>&1
cat todo.txt

Ahora el 1 se va primero a todo.txt y el 2>&1 copia ese destino:

$ bash revisar.sh > todo.txt 2>&1
$ cat todo.txt
pc01: correcto
pc02: correcto
AVISO pc03: disco casi lleno
pc04: correcto
AVISO pc04: sin red

Pantalla limpia y las cinco líneas en todo.txt, en el orden en que las escribió el script.

Si te cuesta recordar el orden, usa &> todo.txt: hace lo mismo y no tiene orden que confundir (es de bash).

Fragmento 3: guardar los fallos y contarlos a la vez

# ¿Qué falla aquí? 3: guardar los fallos y contarlos en la misma línea
grep FAIL accesos.log > fallos.txt | wc -l
cat fallos.txt | wc -l

¿Qué cuenta el primer wc -l?

$ grep FAIL accesos.log > fallos.txt | wc -l
0
$ cat fallos.txt | wc -l
5

Cuenta 0, aunque fallos.txt tiene 5 líneas (el segundo wc lo demuestra). El > fallos.txt se lleva todo el stdout de grep al fichero, así que a la tubería no le queda nada que pasar: wc recibe una entrada vacía. Una salida no puede ir a dos sitios a la vez solo con > y |.

Mezclar > y | en la misma orden esperando que los datos vayan a los dos sitios. La redirección gana y la tubería queda vacía.

El arreglo es tee, que hace exactamente eso: copia al fichero y deja pasar.

# Arreglo: tee guarda la copia y deja pasar los datos hacia wc
grep FAIL accesos.log | tee fallos.txt | wc -l

La salida real:

$ grep FAIL accesos.log | tee fallos.txt | wc -l
5

5, y fallos.txt con sus 5 líneas.

Cuando una orden te devuelva «nada» en una tubería, busca un > escondido en mitad de la línea.

Soluciones

Solución del nivel 1

# Nivel 1: los intentos fallidos, guardados y contados
grep "LOGIN FAIL" accesos.log > fallos.txt  # > crea fallos.txt solo con las líneas que encajan
cat fallos.txt
wc -l < fallos.txt                          # < : el número a secas, sin el nombre del fichero

La salida real:

$ grep "LOGIN FAIL" accesos.log > fallos.txt
$ cat fallos.txt
2026-10-19 08:05 pc02 laura LOGIN FAIL
2026-10-19 08:30 pc04 ana LOGIN FAIL
2026-10-19 08:31 pc04 ana LOGIN FAIL
2026-10-19 10:02 pc01 dani LOGIN FAIL
2026-10-19 10:20 pc02 laura LOGIN FAIL
$ wc -l < fallos.txt
5

Por qué está hecho así:

  • grep "LOGIN FAIL" con comillas porque el patrón tiene un espacio. Con solo FAIL también funcionaría aquí (no hay otro FAIL), pero el patrón completo deja claro qué buscas y no se rompe si mañana aparece un LOGOUT FAIL.
  • > fallos.txt porque queremos un fichero nuevo cada vez; con >>, al repetir el ejercicio se duplicarían las líneas.
  • wc -l < fallos.txt en lugar de wc -l fallos.txt: con < el fichero lo abre bash y wc no conoce su nombre, así que solo escribe 5. La alternativa obvia daría 5 fallos.txt.
  • Cuenta a mano: laura a las 08:05 y 10:20, ana a las 08:30 y 08:31, dani a las 10:02: 2 + 2 + 1 = 5.

Solución del nivel 2

# Nivel 2: quién falla más, guardado y en pantalla a la vez
grep "LOGIN FAIL" accesos.log | cut -d' ' -f4 | sort | uniq -c | sort -nr | tee fallos-por-usuario.txt
wc -l fallos-por-usuario.txt                # una línea por persona que ha fallado

La salida real:

$ grep "LOGIN FAIL" accesos.log | cut -d' ' -f4 | sort | uniq -c | sort -nr | tee fallos-por-usuario.txt
      2 laura
      2 ana
      1 dani
$ wc -l fallos-por-usuario.txt
3 fallos-por-usuario.txt

Por qué está hecho así: la tubería tiene cinco etapas, y cada una deja los datos listos para la siguiente:

EtapaQué saleLíneas
grep "LOGIN FAIL"las 5 líneas de fallos5
cut -d' ' -f4laura, ana, ana, dani, laura5
sortana, ana, dani, laura, laura5
uniq -c2 ana, 1 dani, 2 laura3
sort -nr2 laura, 2 ana, 1 dani3
tee fallos-por-usuario.txtlo mismo, en pantalla y en el fichero3
  • Sin el sort de la tercera etapa, laura saldría partida (es el fragmento 1).
  • sort -nr y no sort -r: aquí todos los números tienen una cifra, pero con un 10 el orden de texto fallaría.
  • tee va al final porque queremos guardar el ranking completo; si hubiera un head detrás, tendría que ir antes que él.
  • laura sale antes que ana porque empatan a 2 y sort -r desempata con el resto de la línea al revés.

Solución del nivel 3

# Nivel 3: informe de uso de cada ordenador, con cabecera y sin ruido de errores
cat << EOF > uso.txt
Uso de los ordenadores del aula 1.3
Generado por $USER
EOF
cat accesos.log martes.log 2> /dev/null | cut -d' ' -f3 | sort | uniq -c >> uso.txt  # martes.log aún no existe
cat uso.txt
tail -n +3 uso.txt | sort -nr | head -1     # desde la línea 3 (sin cabecera): el más usado

La salida real:

$ cat << EOF > uso.txt
> Uso de los ordenadores del aula 1.3
> Generado por $USER
> EOF
$ cat accesos.log martes.log 2> /dev/null | cut -d' ' -f3 | sort | uniq -c >> uso.txt
$ cat uso.txt
Uso de los ordenadores del aula 1.3
Generado por sergio
      4 pc01
      5 pc02
      3 pc03
      4 pc04
$ tail -n +3 uso.txt | sort -nr | head -1
      5 pc02

Por qué está hecho así:

  • La cabecera con << EOF y >. El here-document le da dos líneas a cat y > crea uso.txt con ellas. Sin comillas en EOF, bash cambia $USER por tu usuario (sergio en mi ejecución). Es la única orden con >: todo lo demás añade.
  • cat accesos.log martes.log 2> /dev/null. cat lee los dos ficheros seguidos; como martes.log no existe, se queja por stderr, y 2> /dev/null se traga la queja. Lo que sí pudo leer (las 16 líneas del lunes) sigue por la tubería. El día que exista martes.log, la misma orden lo sumará sin cambiar nada.
  • cut -d' ' -f3 | sort | uniq -c >> uso.txt. El patrón de contar, por ordenador, añadido debajo de la cabecera. Cuenta a mano: pc01 4 + pc02 5 + pc03 3 + pc04 4 = 16.
  • tail -n +3 uso.txt | sort -nr | head -1. tail -n +3 empieza en la línea 3, saltándose la cabecera (con +, «desde la línea N»; sin +, «las N últimas»). Después ordenamos por número y nos quedamos con la primera: pc02.
Uso de cada ordenador del aula 1.3Barras con los registros de cada ordenador: pc01 4, pc02 5, pc03 3 y pc04 4. pc02 es el más usado, el que devuelve la última orden del nivel 3. El error de martes.log, que no existe, se descarta con 2> /dev/null.Uso de cada ordenador del aula 1.3pc014 registrospc025 registros ← el más usadopc033 registrospc044 registrosmartes.log no existe → 2> /dev/null
Las barras son las líneas de uso.txt sin la cabecera; tail -n +3 | sort -nr | head -1 se queda con la más larga.

Si no te saltas la cabecera, sort -nr trata las líneas sin número como un 0 y las deja al final, así que aquí también funcionaría. Pero depender de eso es frágil: con tail -n +3 dices exactamente lo que quieres.

Pruébalo en tu terminal

El fragmento 1 en pequeño, para verlo con tus propios ojos sin necesidad del registro. Teclea estas órdenes y compara:

$ printf 'laura\nana\nana\nlaura\n' | uniq -c
      1 laura
      2 ana
      1 laura
$ printf 'laura\nana\nana\nlaura\n' | sort | uniq -c
      2 ana
      2 laura
$ echo hola > x.txt | wc -l
0

Qué observar:

  1. Sin sort, uniq -c da tres grupos: laura, ana ana y laura otra vez, porque las dos laura no están pegadas.
  2. Con sort, dos grupos: 2 ana y 2 laura. Mismos datos, distinto orden, distinto resultado.
  3. En la última orden, wc -l cuenta 0: el > x.txt se ha llevado el hola al fichero (compruébalo con cat x.txt). Es el fragmento 3.
  4. Cambia la última por echo hola | tee x.txt | wc -l: ahora cuenta 1 y x.txt también tiene hola.

Chuletario: redirecciones y tuberías

Lo esencial en 30 segundos

  • Hay tres flujos: 0 stdin, 1 stdout y 2 stderr. Por defecto, el 0 es el teclado y el 1 y el 2, la pantalla.
  • > vacía y escribe; >> añade. Nunca orden f > f.
  • 2>&1 copia el destino actual del 1: primero > f, después 2>&1.
  • Por | solo viaja stdout. Para meter los errores: 2>&1 |.
  • Para contar: sort | uniq -c | sort -nr. Para guardar y seguir: tee.

Redirecciones

Quiero…CódigoResultado
guardar la salida (desde cero)orden > ff solo tiene la salida nueva
añadir al finalorden >> ff crece
leer un fichero por stdinorden < fla orden no ve el nombre (wc -l < f → 16)
guardar solo los erroresorden 2> err.txtla salida normal sigue en pantalla
guardarlo todoorden > f 2>&1 o orden &> fsalida y errores, en orden
tirar los erroresorden 2> /dev/nullpantalla limpia; $? sigue avisando
escribir un aviso en stderrecho "AVISO" >&2se puede separar con 2>
texto de entrada en la ordencat << EOF > f … EOF$USER se sustituye (con 'EOF', no)

Tuberías y filtros

Quiero…CódigoResultado con accesos.log
líneas que contienen algogrep "LOGIN FAIL" f5 líneas
cuántasgrep -c "LOGIN FAIL" f5
una columnacut -d' ' -f4 flos usuarios
valores distintoscut -d' ' -f3 f | sort -upc01 … pc04
contar repeticionescut -d' ' -f4 f | sort | uniq -c | sort -nr5 laura primero
las N primeras / últimashead -n N / tail -n N
desde la línea Ntail -n +Nsin cabecera
guardar y seguir… | tee f | …f con todo lo que pasó
errores también en la tuberíaorden 2>&1 | filtro

Plantillas de los ejercicios

# Nivel 1: filtrar, guardar y contar sin nombre
grep "LOGIN FAIL" accesos.log > fallos.txt
wc -l < fallos.txt                                  # 5

# Nivel 2: ranking visto y guardado
grep "LOGIN FAIL" accesos.log | cut -d' ' -f4 | sort | uniq -c | sort -nr | tee fallos-por-usuario.txt

# Nivel 3: cabecera + datos + máximo
cat << EOF > uso.txt
Uso de los ordenadores del aula 1.3
Generado por $USER
EOF
cat accesos.log martes.log 2> /dev/null | cut -d' ' -f3 | sort | uniq -c >> uso.txt
tail -n +3 uso.txt | sort -nr | head -1             # 5 pc02

Síntoma → causa probable

SíntomaCausa probable
El fichero se ha quedado vacío (0 bytes)Leíste y escribiste el mismo fichero con >
Mi informe solo tiene el último datoUsaste > en vez de >>
El error sigue saliendo en pantallaSolo redirigiste el 1 (>), o escribiste 2>&1 > f
grep -c cuenta 0 errores con la pantalla llenaLos errores van por stderr: falta 2>&1 antes de |
uniq -c repite un valor con recuentos partidosFalta sort antes de uniq
«100» sale antes que «9»sort sin -n
wc de una tubería da 0Hay un > en mitad de la tubería: usa tee
wc -l escribe el nombre del ficheroUsa wc -l < f

Antes de entregar, pregúntate

  1. ¿Hay algún > que vaya a pisar un fichero que necesito, o el mismo que estoy leyendo?
  2. En cada orden, ¿a dónde va el flujo 1 y a dónde el 2?
  3. Si uso uniq, ¿tengo un sort delante?
  4. Si silencio errores con /dev/null, ¿de verdad no me importan?

Con esto terminamos redirecciones y tuberías. Lo siguiente en Linux son los shell scripts: guardar todas estas órdenes en un fichero y ejecutarlas de una vez.

Sigue aprendiendoTodos los artículos de Linux

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *