When I execute a program in Bash, for example, [ls][2], 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 ls command?

  • Are you talking about how just the output of ls shows up on the terminal, or how $(ls) allows the shell to get the output of ls as a string? – muru Dec 10 '14 at 11:27
  • I'd think that those are the same processes, and since you asked they are not :), so probably the first - how the output of ls shows up on the terminal – Max Koretskyi Dec 10 '14 at 11:30
  • @muru, so which answer is closer to how the output of ls shows up on the terminal? – Max Koretskyi Dec 10 '14 at 11:57
  • I would say mine. Check the update to see if it makes things clearer. – muru Dec 10 '14 at 12:44
  • @muru, ok, thanks! I'll read your updated answer and the stuff about dev/pts a little and get back to you with questions later :) – Max Koretskyi Dec 10 '14 at 12:47

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:

enter image description here

Further reading:

The other case, where I do things like:

echo $(ls)
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.

| improve this answer | |
  • @HaukeLaging how much depth would be proper? An explanation of how an emulator handles the pseudoterminal? – muru Dec 10 '14 at 11:15
  • Sorry, I misunderstood the title (and didn't really read the question because it seemed so clear...). I thought the question was about command substitution. – Hauke Laging Dec 10 '14 at 11:23
  • @HaukeLaging It could be about command substitution, in which case you're right and this answer doesn't make sense. I'll ask OP for a clarification. – muru Dec 10 '14 at 11:26
  • That 1 -> /dev/pts/1 in the fds of gnome-terminal point to the terminal gnome-terminal was launched from. The terminal emulators don't need access to the slave side of the pseudo terminal as they interact with it on the master side (/dev/ptmx). xterm for instance doesn't have any fd open to the slave side after it has started the slave application (usually the shell). – Stéphane Chazelas Dec 10 '14 at 13:02
  • @StéphaneChazelas I launched gnome-terminal from GNOME Shell's run prompt, so shouldn't it go to /dev/null? I tested out xterm and as you said it opens /dev/ptmx. Time for me to go and read what a master side is. – muru Dec 10 '14 at 13:06

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)
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.

| improve this answer | |
  • Are you talking about reading the output of command substitution here? – muru Dec 10 '14 at 11:22
  • thanks, so you answer doesn't apply to this how the output of ls shows up on the terminal? – Max Koretskyi Dec 10 '14 at 12:17
  • @Maximus No. My answer covers the more complicated case of $(/bin/echo foo) instead of echo foo. In your simple case the shell is not involved in the output at all. – Hauke Laging Dec 10 '14 at 12:34
  • @HaukeLaging, I see, thanks. I don't understand muru' answer though. And if it's not involved, how come the output appears on the screen? – Max Koretskyi Dec 10 '14 at 12:40
  • 3
    @Maximus The problem is probably that you don't know what a terminal / pseudo-terminal is. Have a look at Wikipedia: en.wikipedia.org/wiki/Pseudo_terminal Both the shell and the programs it runs write their output to a file descriptor – like they would write to a real file. The terminal they happen to write to makes you see their (printable) output. – Hauke Laging Dec 10 '14 at 12:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.