If you were confused by the preceding example, that was probably because you misunderstood the flow that stderr messages take (and we don't blame you, we confused ourselves there!). While we sent the output of the cat command to /dev/fd/2, we used >, which sends stdout and not stderr.
So in our example, we just abused the stderr file descriptor to print to the Terminal; bad practice. We promise not to do it again. Now then, how can we actually work with stderr messages?
reader@ubuntu:/tmp$ cat /root/
cat: /root/: Permission denied
reader@ubuntu:/tmp$ cat /root/ 1> error-file
cat: /root/: Permission denied
reader@ubuntu:/tmp$ ls -l
-rw-rw-r-- 1 reader reader 0 Nov 5 20:35 error-file
reader@ubuntu:/tmp$ cat /root/ 2> error-file
reader@ubuntu:/tmp$ ls -l
-rw-rw-r-- 1 reader reader 31 Nov 5 20:35 error-file
reader@ubuntu:/tmp$ cat error-file
cat: /root/: Permission denied
This interaction should illustrate some things. First, when cat /root/ throws a Permission denied error, it sends it to stderr instead of stdout. We can see this, because when we do the same command but we try to redirect the standard output with 1> error-file, we still see the output in the Terminal and we also see that error-file is empty.
When instead we use 2> error-file, which redirects stderr instead of regular stdout, we do not see the error message in our Terminal anymore.
Even better, we now see that error-file has 31 bytes of content, and when we print it with cat, we once again see our redirected error message! As mentioned before, and in the same spirit as 1>>, if you'd like to append instead of overwrite the stderr stream to a file, use 2>>.
Now, because it is hard to find a command that prints both stdout and stderr in the same command, we'll create our own: a very simple C program which prints two lines of text, one to stdout and one to stderr.
As a sneak preview into programming and compiling, look at this (don't worry if you don't fully understand this):
reader@ubuntu:~/scripts/chapter_12$ vim stderr.c
reader@ubuntu:~/scripts/chapter_12$ cat stderr.c
#include <stdio.h>
int main()
{
// Print messages to stdout and stderr.
fprintf(stdout, "This is sent to stdout.\n");
fprintf(stderr, "This is sent to stderr.\n");
return 0;
}
reader@ubuntu:~/scripts/chapter_12$ gcc stderr.c -o stderr
reader@ubuntu:~/scripts/chapter_12$ ls -l
total 744
-rw-rw-r-- 1 reader reader 737150 Nov 5 19:45 redirected-file.log
-rw-rw-r-- 1 reader reader 501 Nov 5 20:09 redirect-to-file.sh
-rw-rw-r-- 1 reader reader 84 Nov 5 20:13 redirect-to-file.txt
-rwxrwxr-x 1 reader reader 8392 Nov 5 20:46 stderr
-rw-rw-r-- 1 reader reader 185 Nov 5 20:46 stderr.c
The gcc stderr.c -o stderr command compiles the source code found in stderr.c to the binary stderr.
gcc is the GNU Compiler Collection, and is not always installed by default. If you want to follow along with this example and you're getting an error about not being able to find gcc, install it using sudo apt install gcc -y.
If we run our program, we get two lines of output. Because this is not a Bash script, we cannot execute it with bash stderr. We need to make the binary executable with chmod, and run it with ./stderr:
reader@ubuntu:~/scripts/chapter_12$ bash stderr
stderr: stderr: cannot execute binary file
reader@ubuntu:~/scripts/chapter_12$ chmod +x stderr
reader@ubuntu:~/scripts/chapter_12$ ./stderr
This is sent to stdout.
This is sent to stderr.
Now, let's see what happens when we start redirecting part of this output:
reader@ubuntu:~/scripts/chapter_12$ ./stderr > /tmp/stdout
This is sent to stderr.
reader@ubuntu:~/scripts/chapter_12$ cat /tmp/stdout
This is sent to stdout.
Because we only redirected stdout (last reminder: > equals 1>) to the fully-qualified file /tmp/stdout, the stderr message was still printed to the Terminal.
The other way around gives similar results:
reader@ubuntu:~/scripts/chapter_12$ ./stderr 2> /tmp/stderr
This is sent to stdout.
reader@ubuntu:~/scripts/chapter_12$ cat /tmp/stderr
This is sent to stderr.
Now, when we only redirect stderr using 2> /tmp/stderr, we see the stdout message appear in our Terminal and the stderr is correctly redirected to the /tmp/stderr file.
I'm sure you're asking yourself this question right now: how can we redirect all output, both stdout and stderr, to a file? If this was a book about Bash 3.x, we'd be having a difficult conversation. That conversation would entail us redirecting stderr to stdout, after which we could use > to send all output (because we already diverted stderr to stdout in the first place) to a single file.
Even though that is the logical way to do it, the redirection of stderr to stdout is actually present at the end of the command. The command ends up like this: ./stderr > /tmp/output 2>&1. Not too complex, but hard enough that you never really remember it in one go (you can trust us on this).
Fortunately, in Bash 4.x we have a new redirection command available to us that can do the same thing, but in a much more understandable fashion: &>.