Bash – Pipeline and redirection understanding

bashio-redirection

Please explain this script (from ABSG. Ch. 20):

exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script

Currently I imagine how it works:

  1. script connects fd/3 output to stdout
  2. pipeline connects stdout of ls to stdin of grep (fd/3 is inherited by both processes)
  3. redirerection of stderr of ls to stdout
  4. stdout of ls is changed to fd/3 (so grep process has no more ls output, only errors)
  5. fd/3 is closed for ls

Is this the exactly sequence of redirections?

Can't understand why I see output of ls if there are no errors and we close fd/3 where stdout is redirected.

What is the purpose of closing fd/3 for grep process (grep bad **3>&-**)?

Best Answer

It helps a bit if you think the file descriptors as variables that accept a file as a value (or call it an i/o stream) and the order they appear is the order of their evaluation.

What happens in the above example is:

1) The script starts (as per default and unless otherwise inherited) with the following

fd/0 = stdin    # that's the keyboard
fd/1 = stdout   # that's the screen
fd/2 = stderr   # the screen again, but different stream

2) The exec command translates to declaring a new variable and assigning a value

fd/3 = fd/1  # same as stdout

So now, two file descriptors have the value stdout, i.e. both can be used to print to the screen.

3) before ls is executed and inherits all open file descriptors, the following setup happens

ls.fd/1 = grep.fd/0    # pipe gets precedence, ls.fd/1 writes to grep.stdin
ls.fd/2 = ls.fd/1      # ls.fd/2 writes to grep.stdin 
ls.fd/1 = ls.fd/3      # ls.fd/1 writes to stdout
ls.fd/3 = closed       # fd/3 will not be inherited by `ls`

fd/3 has served the purpose of keeping the stdout value long enough to return it to fd/1. So now everything that ls sends to fd/1 goes to stdout and not grep's stdin.

The order is important, e.g. if we'd run ls -l >&3 2>&1 3>&-, ls.fd/2 would write to stdout instead of grep's stdin.

4) fd/3 for grep is closed and not inherited. It would be unused anyway. grep can only filter error messages from ls

The example provided in ABSG is probably not the most helpful and the comment "Close fd 3 for 'grep' (but not 'ls')" is a bit misleading. You can interpret it as: "for the ls, pass the value of ls.fd/3 to ls.fd/1 before unsetting so it won't get closed".

Related Question