Bash – How does bash actually change stdin/stdout/stderr when using redirection/piping

bashfork

Unfortunately I've had no luck figuring this out, as everything I find is just on the syntax of redirection, or shallow information about how redirection works.

What I want to know is how bash actually changes stdin/stdout/stderr when you use pipes or redirection. If for example, you execute:

ls -la > diroutput.log

How does it change stdout of ls to diroutput.log?

I assume it works like this:

  • Bash runs fork(2) to create a copy of itself
  • Forked bash process sets it's stdout to diroutput.log using something like freopen(3)
  • Forked bash process runs execve(2) or a similar exec function to replace itself with ls which now uses the stdout setup by bash

But that's just my educated guess.

Best Answer

I was able to figure it out using strace -f and writing a small proof of concept in C.

It appears that bash just manipulates file descriptors in the child process before calling execve as I thought.

Here's how ls -la > diroutput.log works (roughly):

  • bash calls fork(2)
  • forked bash process sees the output redirection and opens the file diroutput.log using open(2).
  • forked bash process replaces the stdout file descriptor using the dup2(2) syscall
  • bash calls execve(2) to replace it's executable image with ls which then inherits the already setup stdout

The relevant syscalls look like this (strace output):

6924  open("diroutput.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3 
6924  dup2(3, 1)                        = 1 
6924  close(3)                          = 0 
6924  execve("/bin/ls", ["ls", "-la"], [/* 77 vars */]) = 0
Related Question