stdin
, stdout
y stderr
son secuencias adjuntas 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 terminal o pseudo-terminal (algo así como /dev/pts/0
) en lectura + escritura modo.
Si desde ese shell interactivo, inicia su secuencia de comandos sin utilizar 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, mismos enlaces simbólicos especiales en el archivo real que está abierto en los descriptores de fichero.
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 que Linux que tienen esos archivos especiales).
leer algo de stdin significa leer del descriptor de archivo 0 (que apuntará a algún lugar dentro del archivo al que hace referencia /dev/stdin
).
Pero $(</dev/stdin)
, en el shell no se lee desde stdin, se abre un nuevo descriptor de archivo para leer en el mismo archivo que el abierto en stdin (por lo que se lee desde el inicio del archivo, no hacia dónde apunta actualmente stdin).
Excepto en el caso especial de dispositivos terminales abiertos en modo lectura + escritura, stdout y stderr generalmente no están abiertos para lectura. Están destinados a ser secuencias en las que escribes . Por lo tanto, leer del descriptor de archivo 1 generalmente no funcionará. En Linux, abrir /dev/stdout
o /dev/stderr
leer (como en $(</dev/stdout)
) funcionaría y le permitiría leer del archivo al que va stdout (y si stdout fuera una tubería, eso leería desde el otro extremo de la tubería, y si fuera un zócalo , fallaría ya que no puede abrir un socket).
En nuestro caso, el script se ejecuta sin redireccionamiento en el indicador de un shell interactivo en una terminal, todo / dev / stdin, / dev / stdout y / dev / stderr será ese / dev / pts / x archivo de dispositivo terminal.
La lectura de esos archivos especiales devuelve lo que envía el terminal (lo que escribe en el teclado). Escribirles enviará el texto al terminal (para mostrar).
echo $(</dev/stdin)
echo $(</dev/stderr)
será lo mismo. Para expandir $(</dev/stdin)
, el shell abrirá ese / dev / pts / 0 y leerá lo que escribe hasta que presione ^D
una línea vacía. Luego pasarán la expansión (lo que escribió despojado de las nuevas líneas finales y sujeto a división + glob) a la echo
que luego se enviará en stdout (para mostrar).
Sin embargo en:
echo $(</dev/stdout)
en bash
( y bash
solo ), es importante darse cuenta de que por dentro $(...)
, stdout ha sido redirigido. Ahora es una pipa. En el caso de bash
, un proceso de shell hijo lee el contenido del archivo (aquí /dev/stdout
) y lo escribe en la tubería, mientras el padre lee desde el otro extremo para completar la expansión.
En este caso, cuando se abre ese proceso de bash secundario /dev/stdout
, en realidad está abriendo el extremo de lectura de la tubería. Nada saldrá de eso, es una situación de punto muerto.
Si quisiera leer del archivo señalado por los scripts stdout, lo solucionaría 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
Verías 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 la lectura de /dev/stdin
, /dev/stdout
, /dev/stderr
, que quería leer de la entrada estándar, stdout y stderr (lo que haría aún menos sentido), que 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 comenzó 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 solo para escribir , no para leer, la expansión de $(cat <&3)
y $(cat <&2)
está vacía.
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ía en out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
y en err
:
err
Notarás que no se leyó nada de stdout, porque el anterior printf
había sobrescrito el contenido de out
with what I read from stdin: foo\n
y había dejado la posición stdout dentro de ese archivo justo después. Si te has preparado out
con un texto más grande, como:
echo 'This is longer than "what I read from stdin": foo' > out
Entonces entrarías 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 $(cat <&3)
ha leído lo que quedó después del primero printf
y, al hacerlo, también movió la posición stdout para que el siguiente printf
muestre lo que se leyó después.
echo x
no es lo mismo queecho x > /dev/stdout
si stdout no va a una tubería o algunos dispositivos de caracteres como un dispositivo tty. Por ejemplo, si stdout va a un archivo normalecho x > /dev/stdout
, truncaría el archivo y reemplazaría su contenido enx\n
lugar de escribirx\n
en la posición stdout actual.