nullByteMe
nullByteMe

Reputation: 6391

How does cmd > /dev/null 2>&1 work?

I'm reading up on redirecting data to /dev/null and so I tried a simple test:

ping a.b.c  # which results in an address not found

If I try this:

ping a.b.c > /dev/null # prints the same error message as the one above

However, if I do this:

ping a.b.c > /dev/null 2>&1 # The error message is gone

That last solution is the desired solution, but what is happening with this 2>&1? My research so far suggest that 2 represents stderr and 1 represents stdout. So if I read it that way, it looks like I'm creating a stderr file and redirecting stdout to it?

If that is the case, what does the & in that command do?

Upvotes: 13

Views: 40235

Answers (2)

Evan Rosica
Evan Rosica

Reputation: 1242

Consider the following code which prints the word "stdout" to stdout and the word "stderror" to stderror.

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror

Note that the '&' operator tells bash that 2 is a file descriptor (which points to the stderr) and not a file name. If we left out the '&', this command would print stdout to stdout, and create a file named "2" and write stderror there.

By experimenting with the code above, you can see for yourself exactly how redirection operators work. For instance, by changing which file which of the two descriptors 1,2, is redirected to /dev/null the following two lines of code delete everything from the stdout, and everything from stderror respectively (printing what remains).

$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout

Now, we approach the crux of the question (substituting my example for yours), why does

(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1

produce no output? To truly understand this, I highly recommend you read this webpage on file descriptor tables. Assuming you have done that reading, we can proceed. Note that Bash processes left to right; thus Bash sees >/dev/null first (which is the same as 1>/dev/null), and sets the file descriptor 1 to point to /dev/null instead of the stdout. Having done this, Bash then moves rightwards and sees 2>&1. This sets the file descriptor 2 to point to the same file as file descriptor 1 (and not to file descriptor 1 itself!!!! (see this resource on pointers for more info) . Since file descriptor 1 points to /dev/null, and file descriptor 2 points to the same file as file descriptor 1, file descriptor 2 now also points to /dev/null. Thus both file descriptors point to /dev/null, and this is why no output is rendered.


To test if you really understand the concept, try to guess the output when we switch the redirection order:

(echo "stdout"; echo "stderror" >&2)  2>&1 >/dev/null

stderror

The reasoning here is that evaluating from left to right, Bash sees 2>&1, and thus sets the file descriptor 2 to point to the same place as file descriptor 1, ie stdout. It then sets file descriptor 1 (remember that >/dev/null = 1>/dev/null) to point to >/dev/null, thus deleting everything which would usually be send to to the standard out. Thus all we are left with was that which was not send to stdout in the subshell (the code in the parentheses)- i.e. "stderror". The interesting thing to note there is that even though 1 is just a pointer to the stdout, redirecting pointer 2 to 1 via 2>&1 does NOT form a chain of pointers 2 -> 1 -> stdout. If it did, as a result of redirecting 1 to /dev/null, the code 2>&1 >/dev/null would give the pointer chain 2 -> 1 -> /dev/null, and thus the code would generate nothing, in contrast to what we saw above.


Finally, I'd note that there is a simpler way to do this:

From section 3.6.4 here, we see that we can use the operator &> to redirect both stdout and stderr. Thus, to redirect both the stderr and stdout output of any command to \dev\null (which deletes the output), we simply type $ command &> /dev/null or in case of my example:

$ (echo "stdout"; echo "stderror" >&2) &>/dev/null

Key takeaways:

  • File descriptors behave like pointers (although file descriptors are not the same as file pointers)
  • Redirecting a file descriptor "a" to a file descriptor "b" which points to file "f", causes file descriptor "a" to point to the same place as file descriptor b - file "f". It DOES NOT form a chain of pointers a -> b -> f
  • Because of the above, order matters, 2>&1 >/dev/null is != >/dev/null 2>&1. One generates output and the other does not!

Finally have a look at these great resources:

Bash Documentation on Redirection, An Explanation of File Descriptor Tables, Introduction to Pointers

Upvotes: 3

msb
msb

Reputation: 4428

You are right, 2 is STDERR, 1 is STDOUT. When you do 2>&1 you are saying: "print to STDOUT (1) the things that would go to STDERR (2)". And before that, you said your STDOUT would go to /dev/null. Therefore, nothing is seen. In the examples 1 and 2 you get the output message because it is being printed to STDERR, as a regular redirection only redirects STDOUT.

And when you do the redirection, you are not creating a STDERR, the processes always have a STDERR and a STDOUT when they are created.

Upvotes: 14

Related Questions