45

Given a shell script:

#!/bin/sh

echo "I'm stdout";
echo "I'm stderr" >&2;

Is there a way to call that script such that only stderr would print out, when the last part of the command is 2>/dev/null, ie

$ > sh myscript.sh SOME_OPTIONS_HERE 2>/dev/null
I'm stderr

Or, alternatively:

$ > sh myscript.sh SOME_OPTIONS_HERE >/dev/null
I'm stdout

It's a question at the end of a set of lecture slides, but after nearly a day working at this, I'm nearly certain it's some sort of typo. Pivoting doesn't work. 2>&- doesn't work. I'm out of ideas!

Mateusz Piotrowski
  • 8,029
  • 10
  • 53
  • 79
Richard
  • 1,024
  • 1
  • 7
  • 15

4 Answers4

56
% (sh myscript.sh 3>&2 2>&1 1>&3) 2>/dev/null
I'm stderr
% (sh myscript.sh 3>&2 2>&1 1>&3) >/dev/null 
I'm stdout

Explanation of 3>&2 2>&1 1>&3:

  • 3>&2 means make a copy of file descriptor 2 (fd 2) (stderr), named fd 3 (file descriptor 3). It copies the file descriptor, it doesn't duplicate the stream as tee does.
  • 2>&1 means that fd 2 of sh myscript.sh becomes a copy of it's fd 1 (stdout). Now, when myscript writes to it's stderr (it's fd 2), we receive it on stdout (our fd 1).
  • 1>&3 means that fd 1 of sh myscript.sh becomes a copy of fd 3 (stderr). Now, when myscript writes to it's stdout (it's fd 1), we receive it on stderr (our fd 2).
loxaxs
  • 2,149
  • 23
  • 27
unbeli
  • 29,501
  • 5
  • 55
  • 57
  • That's perfect! Thanks. Can I ask what the significance of the brackets is? – Richard Nov 08 '12 at 23:01
  • 2
    @Richard just for the shell to avoid confusion about double redirect. Can't redirect an FD twice within the same command. – unbeli Nov 09 '12 at 15:35
  • 14
    [Moving the file descriptors](http://www.gnu.org/software/bash/manual/bash.html#Moving-File-Descriptors) using `1>&3-` would be slightly better than just duplicating the file descriptors, since you wouldn't end up with an open FD 3. – 200_success Sep 10 '15 at 01:29
  • 4
    While it has already been said one way or another I would like to emphasize this for the sake of completeness: we might want to close temporary file descriptor after swapping as we don't need it anymore, so the complete example would be: `myscript.sh 3>&2 2>&1 1>&3 3>&- …`. In case of bash it is possible to allocate a file descriptor dynamically: `myscript.sh {FD}>&2 2>&1 1>&$FD {FD}>&-) …`, where FD is a variable name. – Stanislav German-Evtushenko May 02 '17 at 10:59
  • I'm not sure "can't redirect an FD twice within the same command" is a proper explanation for the ()'s. My explanation would be: the stuff within the () is treated as a group, protecting it from participating in the backwards (in my opinion) way in which bash orders sequences of redirections. Alternative ways of writing the first command: `sh myscript.sh 2>/dev/null 3>&2 2>&1 1>&3` or `(((sh myscript.sh 1>&3) 2>&1) 3>&2) 2>/dev/null` – Don Hatch Dec 23 '19 at 18:09
18

For sake of completeness, based on a comment by @200_success above, it is probably better to move the file descriptor 3 using 1>&3- :

$ (sh myscript.sh 3>&2 2>&1 1>&3-) 2>/dev/null
I'm stderr
$ (sh myscript.sh 3>&2 2>&1 1>&3-) >/dev/null 
I'm stdout

Instead of swapping file descriptors on a per-process basis, using exec you can swap stdin & stderr for all following commands launched by the current shell :

$ (exec 3>&2 2>&1 1>&3- ; sh myscript.sh ; sh myscript.sh ) 2>/dev/null
I'm stderr
I'm stderr
$ (exec 3>&2 2>&1 1>&3- ; sh myscript.sh ; sh myscript.sh ) >/dev/null 
I'm stdout
I'm stdout
Sylvain Leroux
  • 50,096
  • 7
  • 103
  • 125
10

Moving a file descriptor (1>&3-) is not portable, not all POSIX shell implementations support it. It is a ksh93-ism and bash-ism. (more info here https://unix.stackexchange.com/questions/65000/practical-use-for-moving-file-descriptors)

It is also possible to close FD 3 instead after performing the redirections.

ls 3>&2 2>&1 1>&3 3>&-

prints the contents of the current working directory out stderr.

The syntax 3>&- or 3<&- closes file descriptor 3.

Greg Nisbet
  • 6,710
  • 3
  • 25
  • 65
9

The bash hackers wiki can be very useful in this kind of things. There's a way of doing it which is not mentioned among these answers, so I'll put my two cents.

The semantics of >&N, for numeric N, means redirect to the target of the file descriptor N. The word target is important since the descriptor can change target later, but once we copied that target we don't care. That's the reason why the order in which we declare of redirection is relevant.

So you can do it as follows:

./myscript.sh 2>&1 >/dev/null

That means:

  1. redirect stderr to stdout's target, that is the stdout output stream. Now stderr copied stdout's target

  2. change stdout to /dev/null. This won't affect stderr, since it "copied" the target before we changed it.

No need for a third file descriptor.

It is interesting how I can't simply do >&-, instead of >/dev/null. This actually closes stdout, so I'm getting an error (on stderr's target, that is the actual stdout, of course :D)

line 3: echo: write error: Bad file descriptor

You can see that order is relevant by trying to swap the redirections:

./myscript.sh >/dev/null 2>&1

This will not work, because:

  1. We set the target of stdout to /dev/null
  2. We set the target of stderr to stdout's target, that is /dev/null again.
Dacav
  • 13,590
  • 11
  • 60
  • 87