echo or print /dev/stdin /dev/stdout /dev/stderr
stdin
, stdout
, and stderr
are streams attached to file descriptors 0, 1, and 2 respectively of a process.
At the prompt of an interactive shell in a terminal or terminal emulator, all those 3 file descriptors would refer to the same open file description which would have been obtained by opening a terminal or pseudo-terminal device file (something like /dev/pts/0
) in read+write mode.
If from that interactive shell, you start your script without using any redirection, your script will inherit those file descriptors.
On Linux, /dev/stdin
, /dev/stdout
, /dev/stderr
are symbolic links to /proc/self/fd/0
, /proc/self/fd/1
, /proc/self/fd/2
respectively, themselves special symlinks to the actual file that is open on those file descriptors.
They are not stdin, stdout, stderr, they are special files that identify what files stdin, stdout, stderr go to (note that it's different in other systems than Linux that have those special files).
reading something from stdin means reading from file descriptor 0 (which will point somewhere within the file referenced by /dev/stdin
).
But in $(</dev/stdin)
, the shell is not reading from stdin, it opens a new file descriptor for reading on the same file as the one open on stdin (so reading from the start of the file, not where stdin currently points to).
Except in the special case of terminal devices open in read+write mode, stdout and stderr are usually not open for reading. They are meant to be streams that you write to. So reading from the file descriptor 1 will generally not work. On Linux, opening /dev/stdout
or /dev/stderr
for reading (as in $(</dev/stdout)
) would work and would let you read from the file where stdout goes to (and if stdout was a pipe, that would read from the other end of the pipe, and if it was a socket, it would fail as you can't open a socket).
In our case of the script run without redirection at the prompt of an interactive shell in a terminal, all of /dev/stdin, /dev/stdout and /dev/stderr will be that /dev/pts/x terminal device file.
Reading from those special files returns what is sent by the terminal (what you type on the keyboard). Writing to them will send the text to the terminal (for display).
echo $(</dev/stdin)
echo $(</dev/stderr)
will be the same. To expand $(</dev/stdin)
, the shell will open that /dev/pts/0 and read what you type until you press ^D
on an empty line. They will then pass the expansion (what you typed stripped of the trailing newlines and subject to split+glob) to echo
which will then output it on stdout (for display).
However in:
echo $(</dev/stdout)
in bash
(and bash
only), it's important to realise that inside $(...)
, stdout has been redirected. It is now a pipe. In the case of bash
, a child shell process is reading the content of the file (here /dev/stdout
) and writing it to the pipe, while the parent reads from the other end to make up the expansion.
In this case when that child bash process opens /dev/stdout
, it is actually opening the reading end of the pipe. Nothing will ever come from that, it's a deadlock situation.
If you wanted to read from the file pointed-to by the scripts stdout, you'd work around it with:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
That would duplicate the fd 1 onto the fd 3, so /dev/fd/3 would point to the same file as /dev/stdout.
With a script like:
#! /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)"
When run as:
echo bar > err
echo foo | myscript > out 2>> err
You'd see in out
afterwards:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
If as opposed to reading from /dev/stdin
, /dev/stdout
, /dev/stderr
, you wanted to read from stdin, stdout and stderr (which would make even less sense), you'd do:
#! /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)"
If you started that second script again as:
echo bar > err
echo foo | myscript > out 2>> err
You'd see in out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr:
and in err
:
bar
cat: -: Bad file descriptor
cat: -: Bad file descriptor
For stdout and stderr, cat
fails because the file descriptors were open for writing only, not reading, the the expansion of $(cat <&3)
and $(cat <&2)
is empty.
If you called it as:
echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err
(where <>
opens in read+write mode without truncation), you'd see in out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
and in err
:
err
You'll notice that nothing was read from stdout, because the previous printf
had overwritten the content of out
with what I read from stdin: foo\n
and left the stdout position within that file just after. If you had primed out
with some larger text, like:
echo 'This is longer than "what I read from stdin": foo' > out
Then you'd get in 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
See how the $(cat <&3)
has read what was left after the first printf
and doing so also moved the stdout position past it so that the next printf
outputs what was read after.
stdout
and stderr
are outputs, you do not read from them you can only write to them. For example:
echo "this is stdout" >/dev/stdout
echo "this is stderr" >/dev/stderr
programs write to stdout by default so the first one is equivalent to
echo "this is stdout"
and you can redirect stderr in other ways such as
echo "this is stderr" 1>&2