When I execute a program in Bash, for example,
[ls], it sends its output to standard output (
fd &1). And the ouput of the executed program is displayed in the terminal. How does Bash/terminal get the output of the
As a child process of the shell,
ls inherits the open file descriptors of the shell. And the standard file descriptors (stdin, stdout, stderr (or 0, 1, 2)) are connected to a pseudo-terminal, which is handled by the terminal emulator.
For example (on a Linux system):
$ ls /proc/$$/fd -l total 0 lrwx------ 1 muru muru 64 Dec 10 16:15 0 -> /dev/pts/3 lrwx------ 1 muru muru 64 Dec 10 16:15 1 -> /dev/pts/3 lrwx------ 1 muru muru 64 Dec 10 16:15 2 -> /dev/pts/3 lrwx------ 1 muru muru 64 Dec 10 16:15 255 -> /dev/pts/3 $ ls /proc/$(pgrep terminator -f)/fd -l | grep pts/3 lrwx------ 1 muru muru 64 Dec 10 16:15 26 -> /dev/pts/3
That is, the output of
ls, or for that matter the shell itself, is not handled by the shell, but by the terminal emulator (GNOME Terminal, terminator, xterm, etc.).
You can test this out:
On Linux, find a pseudoterminal (
pts) used by your terminal emulator (say GNOME Terminal):
$ ls -l /proc/$(pgrep -n gnome-terminal)/fd | grep pts lrwx------ 1 muru muru 64 Dec 10 18:00 1 -> /dev/pts/1 lrwx------ 1 muru muru 64 Dec 10 18:00 15 -> /dev/pts/20 lrwx------ 1 muru muru 64 Dec 10 18:00 2 -> /dev/pts/1
Now, the non-standard fds (those other than 0,1,2) of
gnome-terminal would be used by it to provide input and output for a shell. The terminal emulator reads in data send to that PTS and (after some processing, for colours and such) presents it on the screen. In this case, that would be
15, connected to
pts/20. If I write something to that pts, I can expect it to appear in that terminal:
The other case, where I do things like:
echo $(ls) a=$(date) vim `command -v some_script`
is called Command Substitution. In command substitution, the output of the command is captured by the shell itself, and never reaches the terminal, unless you do print it out (for example,
echo $(ls)). This case is handled in Hauke Laging's answer.
It turns out that I misunderstood the question in the sense of command substitution. Only in that case the shell is involved in output handling.
Hope this is of interest, too...
I attach to a shell with
strace -p 2140 before I run
echo $(/bin/echo foo) in this thell. This is part of the result:
pipe([3, 4]) pipe([5, 6]) ... read(3, "foo\n", 128)
This is what happens in the child process:
dup2(4, 1) close(4) close(3) ... execve("/bin/echo", ...
The shell connects the file descriptors 3 and 4 and then forks. The child process makes fd 4 its
stdout before it runs the new program. Thus everything the child writes to
stdout can be read by the parent shell on its fd 3.