Differences Between “>file 2>&1” and “2>&1 >file” in Bash Redirection
This article is intended to document several common methods of output redirection in Bash, including:
> file
>file 2>&1 vs 2>&1 >file
| (pipe)
Basic Concepts
For system operators, the terms STDIN, STDOUT, and STDERR are likely familiar, corresponding to the three file descriptors within a program — 0, 1, and 2.
In practical terms, when performing file operations within a program, new file descriptors are generated, typically starting from 3, 4, 5, and so on. The built-in 0, 1, 2 do not initially point to any files but instead refer to /dev/tty. This implies that your application can send data to the program through STDIN from tty, and vice versa, it can output different types of output through STDOUT and STDERR to /dev/tty.
Here, we prepare a script with the following content as an example:
#!/bin/bash
echo "Hello stdout"
echo "Hello stderr" 1>&2
It outputs “Hello stdout” to STDOUT and “Hello stderr” to STDERR. The execution example is as follows:
bash-3.2$ bash test.sh
Hello stdout
Hello stderr
Therefore, under the default conditions, the relationship is depicted as follows:
Exploration
Case 1
Everyone learning Linux command operations will inevitably come across the knowledge that to redirect output to a file, you can use “>” followed by the file name. For example, “pwd > my_test” executes the pwd
command and redirects the content of STDOUT to the file "my_test."
As mentioned earlier, STDOUT and STDERR correspond to the numbers 1 and 2, respectively. So, using the notation “2> my_test” means redirecting the content of STDERR to a file.
Let’s practice with the test.sh
script:
bash-3.2$ bash test.sh > haha
Hello stderr
bash-3.2$ cat haha
Hello stdout
In the above example, STDOUT is redirected to the file “haha,” so after execution, “Hello stderr” is still output to /dev/tty, while “Hello stdout” is written to the file.
If we use the “2>” format, the result is reversed:
bash-3.2$ bash test.sh 2> haha2
Hello stdout
bash-3.2$ cat haha2
Hello stderr
File output itself can be used together, for example:
bash-3.2$ bash test.sh > haha 2> haha2
bash-3.2$ cat haha
Hello stdout
bash-3.2$ cat haha2
Hello stderr
In addition to individual outputs to files, references to other file descriptors are possible. For example, using “2>&1” redirects STDERR to STDOUT. Since “2>1” means “redirect STDERR to file 1,” “&1” is added to refer to STDOUT, not a file.
The entire concept is as follows, but since the current output is /dev/tty
, there may not be a noticeable difference in usage:
bash-3.2$ bash test.sh 1>&2
Hello stdout
Hello stderr
Here is the C code example to implement the ‘2>&1’, redirect the STDERR to STDOUT
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
fwrite("For stdout\n", 11, 1, stdout);
fwrite("For stderr\n", 11, 1, stderr);
dup2(STDOUT_FILENO, STDERR_FILENO);
fwrite("To Stdout \n", 11, 1, stdout);
fwrite("To Stderr \n", 11, 1, stderr);
return 0;
}
After the dup2
, all output to the stderr
will be redirect to the same output as the stdout
.
Case 2
After understanding STDOUT and STDERR, a common requirement is to write both STDOUT and STDERR to the same file. The logic is:
- Consolidate the output of stdout and stderr.
- Output the consolidated result to a file.
Therefore, common solutions are “> file 2>&1” and incorrect usage “2>&1 > file.” They look very similar, but their underlying logic is different. Let’s see why the former is correct and the latter fails.
Firstly, for “> file 2>&1,” the logic can be broken down into two parts:
> file
=> Write the output of STDOUT to a file.2>&1
=> Redirect the output of STDERR to the input of STDOUT.
The process is illustrated as follows:
Therefore, both STDOUT and STDERR can be written to the file.
For “2 >&1 > file,” if we break down the logic:
2>&1
=> Redirect the output of STDERR to the output of STDOUT.> file
=> Write the output of STDOUT to the file.
The entire process is as follows, and in the end, only the content of STDOUT is written to the file:
Another simpler notation, “&> file,” achieves the same effect of writing both STDOUT and STDERR to a file:
bash-3.2$ bash test.sh &> qq
bash-3.2$ cat qq
Hello stdout
Hello stderr
Case 3
When using commands, it is common to combine them with the concept of a pipe “|.” The basic idea of “|” is to redirect the “current command’s STDOUT” to the “STDIN of the next command,” as shown in the following flow:
In the example below, only STDOUT is redirected to the grep
command, while STDERR is still output to /dev/tty:
bash-3.2$ bash test.sh | grep haha
Hello stderr
If you need to send the content of STDERR through the pipe as well, the concept is similar to writing to a file, requiring:
- Redirect STDERR to STDOUT.
- Connect the STDIN of the next command to the STDOUT of the current command.
bash-3.2$ bash test.sh 2>&1 | grep xxx
bash-3.2$ bash test.sh 2>&1 | grep err
Hello stderr
Summary
This article briefly documents common redirection techniques in Bash. Once you understand the principles, you don’t need to memorize how to redirect STDERR and STDOUT to the same file. You can think more critically about how to achieve various requirements.