Cuando un proceso arranca en Linux, el sistema operativo le entrega tres canales de comunicación ya abiertos, sin que el programa tenga que pedirlos. Se llaman stdin, stdout y stderr, y están identificados por los números 0, 1 y 2 respectivamente. Son file descriptors, es decir, referencias numéricas a recursos de I/O que el kernel gestiona por cada proceso. No son ficheros en disco, aunque se comporten como si lo fueran: son abstracciones que pueden apuntar al teclado, a la pantalla, a un pipe, a un socket, o a cualquier cosa que el sistema operativo sepa leer o escribir.

El diseño de tres canales separados no es arbitrario. stdin (fd 0) es la entrada: por defecto apunta al teclado, y el proceso lee de ahí cuando espera datos del usuario o de otro programa. stdout (fd 1) es la salida normal: por defecto apunta a la terminal, y el proceso escribe ahí sus resultados, sus datos, lo que quiere comunicar hacia afuera. stderr (fd 2) es la salida de errores y diagnóstico: también apunta a la terminal por defecto, pero es un canal completamente independiente de stdout.

Esa independencia es el punto clave. Si stdout y stderr fueran el mismo canal, no podrías encadenar programas de forma fiable. Cuando escribes programa_a | programa_b, el shell conecta el stdout de programa_a al stdin de programa_b. Si programa_a mezcla errores con datos en el mismo stream, programa_b recibiría basura entre los datos útiles. Al tener stderr separado, los mensajes de error van directamente a tu terminal sin contaminar el pipe. stdout es para datos. stderr es para mensajes al operador.

¿Cuándo te importa saber esto? En cuanto empieces a redirigir salida, a encadenar comandos con pipes, o a escribir scripts que otros programas van a consumir. Si no entiendes que un mensaje de error aparece en pantalla aunque hayas redirigido la salida a un fichero, pasarás un buen rato desconcertado. Y si mezclas los dos streams sin querer en un script, el programa siguiente en la cadena leerá mensajes de error como si fueran datos válidos.

# Ejecutamos un comando que produce salida normal Y un error
# ls lista el directorio actual (stdout) y falla con el que no existe (stderr)
ls /tmp /directorio_que_no_existe

# Para ver que son canales distintos, redirigimos solo stdout a un fichero
# El error sigue apareciendo en la terminal porque stderr no está redirigido
ls /tmp /directorio_que_no_existe > /tmp/salida_normal.txt

# Comprobamos qué capturó el fichero: solo los datos de stdout
cat /tmp/salida_normal.txt

# Ahora redirigimos solo stderr a otro fichero
# La salida normal aparece en la terminal; el error va al fichero
ls /tmp /directorio_que_no_existe 2> /tmp/solo_errores.txt

# Verificamos el fichero de errores
cat /tmp/solo_errores.txt

# echo siempre escribe en stdout
echo "este mensaje va a stdout"

# Para escribir en stderr desde bash hay que redirigir explícitamente al fd 2
echo "este es un error" >&2

Fíjate en lo que pasa con el primer ls: ves tanto la lista de /tmp como el mensaje de error, y visualmente parecen lo mismo porque los dos van a la terminal. Esa ilusión es exactamente lo que lleva a confusión. El shell los está escribiendo por separado, en canales distintos, pero tu emulador de terminal los mezcla en pantalla porque ambos terminan apuntando al mismo destino físico.

Cuando añades > /tmp/salida_normal.txt, le dices al shell que conecte el fd 1 de ls a ese fichero. El fd 2 no se toca: sigue apuntando a la terminal. Por eso el error aparece en pantalla aunque hayas redirigido la salida. No es que la redirección falle, es que redirigiste el canal correcto y el error viaja por el canal que no tocaste.

La sintaxis 2> hace lo mismo pero para el fd 2: conecta stderr al fichero indicado, dejando stdout intacto apuntando a la terminal. El número antes del > es literalmente el número del file descriptor.

La última línea del ejemplo, echo "este es un error" >&2, muestra cómo un script puede escribir deliberadamente en stderr. La notación >&2 significa “redirige mi stdout al mismo destino al que apunta el fd 2”. En un script bien escrito, los mensajes de diagnóstico, advertencias y errores van a stderr, y los datos que otro programa va a consumir van a stdout. Así cualquiera que use tu script puede separar unos de otros sin adivinar nada.

40