stdin
, stdout
y stderr
son flujos adjunto a los descriptores de archivo 0, 1 y 2 respectivamente de un proceso.
En el indicador de un shell interactivo en un terminal o emulador de terminal, todos esos 3 descriptores de archivo se referirían a la misma descripción de archivo abierto que se habría obtenido al abrir un archivo de dispositivo de terminal o pseudo-terminal (algo así como /dev/pts/0
) en modo lectura+escritura.
Si desde ese shell interactivo, inicia su secuencia de comandos sin usar ninguna redirección, su secuencia de comandos heredará esos descriptores de archivo.
En Linux, /dev/stdin
, /dev/stdout
, /dev/stderr
son enlaces simbólicos a /proc/self/fd/0
, /proc/self/fd/1
, /proc/self/fd/2
respectivamente, ellos mismos enlaces simbólicos especiales al archivo real que está abierto en esos descriptores de archivo.
No son stdin, stdout, stderr, son archivos especiales que identifican a qué archivos van stdin, stdout, stderr (tenga en cuenta que es diferente en otros sistemas además de Linux que tienen esos archivos especiales).
leer algo desde stdin significa leer desde el descriptor de archivo 0 (que apuntará a algún lugar dentro del archivo al que hace referencia /dev/stdin
).
Pero en $(</dev/stdin)
, el shell no está leyendo desde stdin, abre un nuevo descriptor de archivo para leer en el mismo archivo que el que está abierto en stdin (por lo que lee desde el inicio del archivo, no donde apunta actualmente stdin).
Excepto en el caso especial de dispositivos terminales abiertos en modo lectura+escritura, stdout y stderr normalmente no están abiertos para lectura. Están destinados a ser flujos en los que escribes . Por lo tanto, la lectura del descriptor de archivo 1 generalmente no funcionará. En Linux, abriendo /dev/stdout
o /dev/stderr
para leer (como en $(</dev/stdout)
) funcionaría y le permitiría leer desde el archivo a donde va stdout (y si stdout fuera una tubería, eso leería desde el otro extremo de la tubería, y si fuera un socket, fallaría porque no puede abrir un enchufe).
En nuestro caso, el script se ejecuta sin redirección en el indicador de un shell interactivo en una terminal, todo /dev/stdin, /dev/stdout y /dev/stderr será ese archivo de dispositivo de terminal /dev/pts/x.
La lectura de esos archivos especiales devuelve lo que envía el terminal (lo que escribe en el teclado). Escribirles enviará el texto a la terminal (para mostrar).
echo $(</dev/stdin)
echo $(</dev/stderr)
será lo mismo. Para expandir $(</dev/stdin)
, el shell abrirá /dev/pts/0 y leerá lo que escriba hasta que presione ^D
en una línea vacía. Luego pasarán la expansión (lo que escribió sin las líneas nuevas finales y sujeto a split+glob) a echo
que luego lo generará en stdout (para visualización).
Sin embargo en:
echo $(</dev/stdout)
en bash
(y bash
solamente), es importante darse cuenta de que dentro de $(...)
, stdout ha sido redirigido. Ahora es una pipa. En el caso de bash
, un proceso de shell secundario está leyendo el contenido del archivo (aquí /dev/stdout
) y escribirlo en la tubería, mientras que el padre lee desde el otro extremo para hacer la expansión.
En este caso, cuando ese proceso bash secundario abre /dev/stdout
, en realidad está abriendo el extremo de lectura de la tubería. Nunca saldrá nada de eso, es una situación de punto muerto.
Si quisiera leer del archivo señalado por los scripts stdout, trabajaría alrededor con:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
Eso duplicaría el fd 1 en el fd 3, por lo que /dev/fd/3 apuntaría al mismo archivo que /dev/stdout.
Con un guión como:
#! /bin/bash -
printf 'content of file on stdin: %s\n' "$(</dev/stdin)"
{ printf 'content of file on stdout: %s\n' "$(</dev/fd/3)"; } 3<&1
printf 'content of file on stderr: %s\n' "$(</dev/stderr)"
Cuando se ejecuta como:
echo bar > err
echo foo | myscript > out 2>> err
Lo verías en out
después:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
Si en lugar de leer desde /dev/stdin
, /dev/stdout
, /dev/stderr
, quería leer desde stdin, stdout y stderr (lo que tendría aún menos sentido), haría:
#! /bin/sh -
printf 'what I read from stdin: %s\n' "$(cat)"
{ printf 'what I read from stdout: %s\n' "$(cat <&3)"; } 3<&1
printf 'what I read from stderr: %s\n' "$(cat <&2)"
Si comenzaste ese segundo script nuevamente como:
echo bar > err
echo foo | myscript > out 2>> err
Lo verías en out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr:
y en err
:
bar
cat: -: Bad file descriptor
cat: -: Bad file descriptor
Para stdout y stderr, cat
falla porque los descriptores de archivo estaban abiertos para escribir solo, sin leer, la expansión de $(cat <&3)
y $(cat <&2)
está vacío.
Si lo llamaste como:
echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err
(donde <>
se abre en modo lectura+escritura sin truncamiento), verá en out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
y en err
:
err
Notará que no se leyó nada de la salida estándar, porque el printf
anterior había sobrescrito el contenido de out
con what I read from stdin: foo\n
y dejó la posición de salida estándar dentro de ese archivo justo después. Si hubiera preparado out
con un texto más grande, como:
echo 'This is longer than "what I read from stdin": foo' > out
Entonces entrarías en out
:
what I read from stdin: foo
read from stdin": foo
what I read from stdout: read from stdin": foo
what I read from stderr: err
Vea cómo el $(cat <&3)
ha leído lo que quedó después del primer printf
y al hacerlo también movió la posición stdout más allá para que el siguiente printf
muestra lo que se leyó después.
stdout
y stderr
son salidas, no las lee, solo puede escribir en ellas. Por ejemplo:
echo "this is stdout" >/dev/stdout
echo "this is stderr" >/dev/stderr
los programas escriben en stdout de forma predeterminada, por lo que el primero es equivalente a
echo "this is stdout"
y puede redirigir stderr de otras maneras, como
echo "this is stderr" 1>&2