6

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?

5
  • 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
    Commented Dec 10, 2014 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 Commented Dec 10, 2014 at 11:30
  • @muru, so which answer is closer to how the output of ls shows up on the terminal? Commented Dec 10, 2014 at 11:57
  • I would say mine. Check the update to see if it makes things clearer.
    – muru
    Commented Dec 10, 2014 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 :) Commented Dec 10, 2014 at 12:47

2 Answers 2

9

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

11
  • @HaukeLaging how much depth would be proper? An explanation of how an emulator handles the pseudoterminal?
    – muru
    Commented Dec 10, 2014 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. Commented Dec 10, 2014 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
    Commented Dec 10, 2014 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). Commented Dec 10, 2014 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
    Commented Dec 10, 2014 at 13:06
3

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.

6
  • Are you talking about reading the output of command substitution here?
    – muru
    Commented Dec 10, 2014 at 11:22
  • thanks, so you answer doesn't apply to this how the output of ls shows up on the terminal? Commented Dec 10, 2014 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. Commented Dec 10, 2014 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? Commented Dec 10, 2014 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. Commented Dec 10, 2014 at 12:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .