0

I found a magic command here

./my.sh 3>all 1> >(tee out >&3) 2> >(tee err >&3)

I am confused at some places:

  1. Does 3>all mean setting the file descriptor 3 for file all?
  2. What do 1> > and 2> >? As per my understanding, the command should be ./my.sh 3>all 1>(tee out >&3) 2>(tee err >&3). But that doesn't work.
  3. Why doesn't (tee err >&3) overwrite the file all?

This is my my.sh

#!/bin/bash

echo myecho
ls dflj
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
yode
  • 1,047

2 Answers2

4

You can read about this syntax in the bash man page under Process substitution:

>(list). The process list is run with its output connected to some file in /dev/fd. The name of this file is passed as an argument to the current command as the result of the expansion.

Look at the output of this command which does not do any redirection:

echo >(echo hi >/tmp/a) >(echo lo >/tmp/b)

it is (on my system):

/dev/fd/63 /dev/fd/62

So you must read 1> >(...) as 1> and >(...). The second part gets replaced by /dev/fd/63, and we then have 1> /dev/fd/63 so we redirect stdout to file descriptor 63.

bash runs the command inside >(...) in a separate process, and connects the stdin of that process to file descriptor 63. Check out this example:

set -x
echo hello > >(cat -n)

The stdout of the echo is piped into the input of cat -n and you get:

+ echo hello
++ cat -n
 1  hello

Perhaps what you are missing is that when you have a file descriptor (fd) for a file, and then fork the process (which bash is doing with >(...)), you can inherit the same fd in the new process. So the 2 processes share the same fd. Moreover, there is only one file offset for an fd, so if process 1 writes 3 characters to the fd, then the offset moves from 0 to 3. If process 2 then writes 5 characters to the fd, the data is placed at offset 3 and the offset becomes 8. If process 1 writes another character it is placed at offset 8, and so on. This is how the two tee commands in your question manage to write to the same file all without overwriting each other.

Using >&3 does not create a new fd; it simply closes the current stdout fd 1, then renumbers fd 3 to fd 1. So there is still only one fd for the two processes, even if the number now seen by each process is different (see man dup2 for the underlying system call).

meuh
  • 51,383
  • I have read your answer many times,and I have read thoes related links.But I don't know the 3>all mean what.And I don't know why the file all will contain all information after (tee out >&3) and (tee err >&3). I even think the err information should cover the former out information.Then the file all just contain err.Could you edit your answer to explain it? – yode Jul 10 '17 at 10:49
  • I've added some explanation to the end of my answer. Also check out Rabban's answer. – meuh Jul 10 '17 at 14:10
2

To try to explain more literally than meuh's great theoretical anwser - as you are probably aware, there are a number of default file descriptors:

  • 0 stands for stdin
  • 1 stands for stdout
  • 2 stands for stderr

What your command does is the following:

  • 3>all open a new file descriptor pointing to file all
  • 1> >(tee out >&3) redirect stdout (1) to the file descriptor opened and returned by the tee command, as explained by meuh
    • tee out >&3 redirects its input (in this case, the stdout from your script) to a file named out, and wherever file descriptor 3 points to (in this case the file all)
  • 2> >(tee err >&3) redirect stderr (2) to the file descriptor opened and returned by the tee command, as explained by meuh
    • tee err >&3 redirects its input (in this case, the stderr from your script) to a file named err, and wherever file descriptor 3 points to (in this case the file all)

Judging by your comment I think that the thing that is confusing you is that you are expecting that you'd need to use >> as the redirection operator if you wanted to append output to a file.

This is not the case here, as all you're literally doing is connecting both the stdout and stderr to the file descriptor that points to the file all.

The effect is the same as doing:

./my.sh > all 2>&1

Which first redirects stdout to the file all, then redirects stderr to wherever stdout is pointing.

user6075516
  • 898
  • 6
  • 11