[vox-tech] quick, stupid bash question

vox-tech@lists.lugod.org vox-tech@lists.lugod.org
Wed, 29 May 2002 17:32:23 -0400


On Wed, May 29, 2002 at 03:18:08PM -0400, msimons@moria.simons-clan.com wrote:
> On Wed, May 29, 2002 at 11:29:29AM -0700, Peter Jay Salzman wrote:
> > begin nbs <nbs@sonic.net> 
> > >      strace lsof 2>&1 1> /dev/null | grep
> >  
> > ok, haven't tried this, but this looks to me like:
> > 
> > put stderr into stdout
> > redirect stdout (and therefore stderr) into /dev/null
> > pipe stdout (which should be null) to grep.
> 
>   The order of operation of file operators is right to left.  

On Wed, May 29, 2002 at 12:32:45PM -0700, Shawn P. Neugebauer wrote:
> The order of redirects is left to right.

  Based on Shawn's comment I've done some tests and operation is very
much left to right like he says.  Now I have a better understanding of 
how dup2 works... but I hope I really don't need to use this anytime 
soon.


>  ( strace ls -R / 2>&1 1>&3 | grep open; ) 3>&1

  Since I'm revising most of this email I would now recommend the 
following...

  ( exec 3>&1 4>&2; strace ls -R / 2>&1 1>&3 | grep open 1>&4 ) 

  It does a similar thing to the one above... but has the nicer property of 
after the ')' the grepped output from strace is still going to stderr and
ls going to stdout, which the first version doesn't do (they are both going
to stdout).  This way you can redirect view them correctly or split them
into other places later.


> > >      strace lsof 2>&1 1> /dev/null | grep
>
>   So the first operation "| grep" spawns a copy of grep on the 
> stdout of a future output of a new pipe.  The second "1> /dev/null" 
> takes stdout and throws it away.  The third "2>&1" takes stderr and 
> redirects it to stdout.

In the parent
============
A - '|'
  The first thing that happens is a pipe (see pipe(2) manpage) because '|'
appears in the command.  This call creates two fds (3 and 4) which are 
attached together... and is to handle communication between strace
and grep.
============

Fork child 1
============
A -  '|'
  The child closes fd 3, which is for pipe reading.  invokes dup2(4, 1) which
attaches the fd 1 to the output channel on the pipe and closes fd 4.

B - '2>&1'
  Invoke the dup2(2, 1) system call, which closes the old stderr 
(which was going to the user's terminal) and makes fd 2 (point at the same 
place as stdout, which in this case can be thought of as the user's terminal).

C - '1> /dev/null'
  Open /dev/null for writing, then dup2(X, 1) which attaches a new 'stdout' 
to the /dev/null file, and close(X).

D - then exec the command "strace ..."
============

Back in the parent
============
A - the parent closes fd 4, since it is meant for writing by child 1.

E - the parent forks another child

A - the parent closes fd 3, since it is meant for reading by child 2.

(there appears to be a bug in bash that the parent of a pipe creation
 closes both the input and output channels twice of a pipe twice).
============

Fork child 2
============
F - Call dup2(3, 0) which attaches the input pipe fd 3 to this 
    processes stdin.

G - then exec the command "grep ..."
============


  Here are the actual calls captured by strace... the letters in the
first column are supposed to help understand which step above is 
being processed at the time.

in one window
=====
msimons@star:~$ echo $$
693
msimons@star:~$ strace ls / 2>&1 1> /dev/null | grep open
=====

in another window
====
msimons@star:~$ strace -f -p 693 -e execve,close,dup2,open,fork
A [pid   683] pipe([3, 4])                            = 0
  [pid   693] fork()                      = 31806

A [pid 31806] close(3)                    = 0
A [pid 31806] dup2(4, 1)                  = 1
A [pid 31806] close(4)                    = 0
B [pid 31806] dup2(1, 2)                  = 2
C [pid 31806] open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
C [pid 31806] dup2(3, 1)                  = 1
C [pid 31806] close(3)                    = 0
D [pid 31806] execve("/usr/bin/strace", ["strace", "ls", "/"], [/* 27 vars */]) =  0

A [pid   693] close(4)                    = 0
A* [pid   693] close(4)                    = -1 EBADF (Bad file descriptor)
E [pid   693] fork()                      = 31808
A [pid   693] close(3)                    = 0
A* [pid   693] close(3)                    = -1 EBADF (Bad file descriptor)

F [pid 31808] dup2(3, 0)                  = 0
F [pid 31808] close(3)                    = 0
G [pid 31808] execve("/bin/grep", ["grep", "open"], [/* 27 vars */]) = 0
====


>  ( strace ls -R / 2>&1 1>&3 | grep open; ) 3>&1

  here is a correct breakdown of the command-line above in order of 
operation...
=========
- fork a child_A for '3>&1'
- in child_A
  - dup2(1, 3) for '3>&1'
  - create pipe for '|'
  - fork child_B for for 'strace'
  - close pipe out channel (twice) for '|'
  - fork child_C for grep
  - close pipe in channel (twice) for '|'

- in child_B (strace)
  - close pipe input channel for '|'
  - attach stdout to pipe output channel, 
    then close pipe out channel fd 5 for '|'
  - attach stderr to stdout for '2>&1'
  - attach stdout to fd 3 for '3>&1'
  - exec strace...

- in child_C (grep)
  - attach stdin to pipe input channel, 
    then close pipe in channel fd 4 for '|'
  - exec grep
=========