7

Early in a script, I see this:

exec 3>&2

And later:

{ $app $conf_file &>$app_log_file & } 1>&3 2>&1

My understanding of this looks something like this:

  • Create fd 3
  • Redirect fd 3 output to stderr
  • (Upon app execution) redirect stdout to fd 3, then redirect stderr to stdout

Isn't that some kind of circular madness? 3>stderr>stdout>3>etc?

I'm especially concerned as to the intention/implications of this line because I'd like to start running some apps using this script with valgrind. I'd like to see valgrind's output interspersed with the app's log statements, so I'm hoping that the default output of stderr is captured by the confusing line above. However, in some of the crashes that have led me to wanting to use valgrind, I've seen glibc errors outputted straight to the terminal, rather than captured in the app's log file.

So, the question(s): What does that execution line do, exactly? Does it capture stderr? If so, why do I see glibc output on the command line when an app crashes? If not, how should I change it to accomplish this goal?

pdm
  • 1,027
  • 1
  • 9
  • 24

3 Answers3

6

You misread the 3>&2 syntax. It means open fd 3 and make it a duplicate of fd 2. See Duplicating File Descriptors.

In the same way 2>&1 does not mean make fd 2 point to the location of fd 1 it means re-open fd 2 as a duplicate of fd 1 (mostly the same net effect but different semantics).

Also remember that all redirections occur as they happen and that there are no "pointers" here. So 2>&1 1>/dev/null does not redirect standard error to /dev/null it leaves standard error attached to wherever standard output had been attached to (probably the terminal).

So the code in question does this:

  1. Open fd 3 as a duplicate of fd 2
  2. Re-open fd 1 as a duplicate of fd 3
  3. Re-open fd 2 as a duplicate of fd 1

Effectively those lines send everything to standard error (or wherever fd 2 was attached when the initial exec line ran). If the redirections had been 2>&1 1>&3 then they would have swapped locations. I wonder if that was the original intention of that line since, as written, it is fairly pointless.

Not to mention that with the redirection inside the brace list the redirections on the outside of the brace list are fairly useless.

Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • Without the `&>logfile`, all 3 file descriptors will be redirected to `stderr` for `$app`. Because `2>&1` happens AFTER `1>&3`. This is why I just tested it with `sleep` instead of trying to puzzle through someone else's possibly-weird logic. :) `2>&1 1>&3` would swap. – Peter Cordes Jul 08 '15 at 19:31
  • @PeterCordes Hah, true. I wonder if that's an error in the original (that sort of thing happens all the time). – Etan Reisner Jul 08 '15 at 19:41
2

Ok, well let's see what happens in practice:

peter@tesla:/tmp/test$ bash -c 'exec 3>&2; { sleep 60m &>logfile & } 1>&3 2>&1'  > stdout 2>stderr
peter@tesla:/tmp/test$ psg sleep
peter    22147  0.0  0.0   7232   836 pts/14   S    15:51   0:00 sleep 60m
peter@tesla:/tmp/test$ ll /proc/22147/fd
total 0
lr-x------ 1 peter peter 64 Jul  8 15:51 0 -> /dev/null
l-wx------ 1 peter peter 64 Jul  8 15:51 1 -> /tmp/test/logfile
l-wx------ 1 peter peter 64 Jul  8 15:51 2 -> /tmp/test/logfile
l-wx------ 1 peter peter 64 Jul  8 15:51 3 -> /tmp/test/stderr

I'm not sure exactly why the author of your script ended up with that line of code. Presumably it made sense to them when they wrote it. The redirections outside the curly braces happen before the redirections inside, so they're both overriden by the &>logfile. Even errors from bash, like command not found would end up in the logfile.

You say you see glibc messages on your terminal when the app crashes. I think your app must be using fd 3 after it starts. i.e., it was written to be started from a script that opened fd 3 for it, or else it opens /dev/tty or something.

BTW, psg is a function I define in my .bashrc:
psg(){ ps aux | grep "${@:-$USER}" | grep -v grep; }
recently updated to:

psg(){  local pids=$(pgrep -f "${@:--u$USER}"); [[ $pids ]] && ps u   -p $pids; }
psgw(){ local pids=$(pgrep -f "${@:--u$USER}"); [[ $pids ]] && ps uww -p $pids; }
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Unrelated to this answer but use `pgrep` instead of `ps | grep`. – Etan Reisner Jul 08 '15 at 18:59
  • An error running `$app` will go to standard output I believe. Oh, no it will go to the app log file because of the `&>$app_log_file` I think. – Etan Reisner Jul 08 '15 at 18:59
  • Thanks for the `pgrep` tip. I've had `psg` in my bashrc for about 15 years, never bothered to see if there was anything more optimal. :) – Peter Cordes Jul 08 '15 at 19:02
  • Yup, just tested by replacing `sleep` with a command that doesn't exist. The `command not found` error ends up in the logfile, because the innermost redirections happen last. The OP said some glibc errors show on the terminal when the app crashes, so maybe the app fiddles with fd `3` after it starts. Its stderr and stdout are both connected to the logfile. – Peter Cordes Jul 08 '15 at 19:09
  • glibc errors are likely something else and probably bypass even the shell. – Etan Reisner Jul 08 '15 at 19:13
  • How can they "bypass the shell"? They're written with Unix `write(2)` system calls to Unix file descriptors. Unless glibc opens `/dev/tty` on its own, or something, to get a FD for printing error messages, they will go wherever you point `stderr`. Or maybe the script starts more than just `$app`, and some of the other processes it starts do have their stdout or stderr connected to the terminal. – Peter Cordes Jul 08 '15 at 19:20
  • Guys, you and your debug techniques rule. I fave this topic for future references! – Eugeniu Rosca Jul 08 '15 at 21:44
0

You need a context first, as in @Peter Cordes example. He provided the context by setting >stdout and 2>stderr first.

I have modified his example a bit.

$ bash -c 'exec 3>&2; { sleep 60m & } 1>&3 2>&1'  >stdout 2>stderr
$ ps aux | grep sleep
logan     272163  0.0  0.0   8084   580 pts/2    S    19:22   0:00 sleep 60m
logan     272165  0.0  0.0   8912   712 pts/2    S+   19:23   0:00 grep --color=auto sleep
$ ll /proc/272163/fd
total 0
dr-x------ 2 logan logan  0 Aug 25 19:23 ./
dr-xr-xr-x 9 logan logan  0 Aug 25 19:23 ../
lr-x------ 1 logan logan 64 Aug 25 19:23 0 -> /dev/null
l-wx------ 1 logan logan 64 Aug 25 19:23 1 -> /tmp/tmp.Vld71a451u/stderr
l-wx------ 1 logan logan 64 Aug 25 19:23 2 -> /tmp/tmp.Vld71a451u/stderr
l-wx------ 1 logan logan 64 Aug 25 19:23 3 -> /tmp/tmp.Vld71a451u/stderr

First, exec 3>&2 sets fd3 to point to stderr file. Then 1>&3 sets fd1 to point to stderr file also. Lastly, 2>&1 sets fd2 to point to stderr file too! (don't get confused with stderr fd2 and in this case stderr just being a random file name)

The reason fd0 is set to /dev/null, I'm guessing, is because the command is run in a non-interactive shell.

Logan Lee
  • 807
  • 9
  • 21