File descriptors and redirection are among the most important Bash concepts to understand if you want to write reliable shell scripts. They can also be surprisingly confusing: a single misplaced > or 2>&1 can change where your output goes, hide an error message, or even overwrite a file you wanted to keep.
The key to understanding them is to stop thinking of a command as something that simply prints text on the screen. Every command has input and output channels, and Bash lets you connect those channels to files, other commands, or even nowhere at all.
In this article, we'll explore how Bash handles input and output, what file descriptors are, and how to redirect data wherever we need it. We'll start with the basics, then move on to combining redirections, handling errors, working with pipelines, and using file descriptors in scripts.
The examples are designed to be tried directly in your terminal. Once you understand the underlying rules, even complicated redirection expressions become much easier to read.
1. What Is a File Descriptor?
A file descriptor (FD) is a small integer that a process uses to refer to an open file or another input/output resource.
Despite the name, a file descriptor doesn't necessarily refer to a regular file on disk. It can refer to a terminal, a pipe, a socket, or another resource through which a process reads or writes data.
When you run a command in Bash, it normally starts with three standard file descriptors:
File descriptor | Name | Abbreviation | Typical destination |
|---|---|---|---|
| Standard input |
| Keyboard or input from another command |
| Standard output |
| Terminal screen |
| Standard error |
| Terminal screen |
These three descriptors are the foundation of shell redirection.
How the three standard streams work
File descriptor 0 — stdin
The command reads input from here.
File descriptor 1 — stdout
The command sends normal output here.
File descriptor 2 — stderr
The command sends diagnostic messages and errors here.
Why are standard output and standard error separate?
Imagine that a command processes hundreds of files. You want to save the successful results to a file while still seeing any errors on your terminal. Separate file descriptors let you do exactly that.
For example:
find /etc -type f -print > files.txtThe list of files goes into files.txt. If find encounters an error, such as a permission problem, the diagnostic message normally goes to standard error and remains visible in your terminal.
You can redirect those two streams independently, which is what makes file descriptors so useful.
2. Redirecting Standard Output
The simplest redirection is >.
command > output.txtThis tells Bash to open output.txt for writing and connect the command's standard output to that file.
For example:
printf 'Hello, world!\n' > greeting.txtNothing appears on the terminal because the output is written to greeting.txt.
You can inspect the result with:
cat greeting.txtOutput:
Hello, world!Overwriting a file
When you use > with an existing regular file, Bash normally truncates the file before the command runs.
For example:
printf 'First version\n' > greeting.txt
printf 'Second version\n' > greeting.txtThe file contains only:
Second versionThe first line has been replaced.
Be careful with >: it can destroy existing file contents even if the command subsequently fails or produces no output.
Appending to a file
If you want to add output without replacing the existing contents, use >>:
printf 'First line\n' > output.txt
printf 'Second line\n' >> output.txtNow the file contains:
First line
Second lineThe difference is simple:
>creates or truncates the destination file.>>creates the file if necessary and appends output to it.
Appending is useful for log files, reports, and scripts that collect results over time.
Preventing accidental overwrites with noclobber
Bash has an option called noclobber, which prevents ordinary output redirection from overwriting an existing regular file.
Enable it with:
set -o noclobberNow this command fails if output.txt already exists:
printf 'New data\n' > output.txtYou can still append with >>.
If you deliberately want to overwrite a file despite noclobber, use >|:
printf 'Replacement data\n' >| output.txtThis forces the output redirection.
To disable noclobber again:
set +o noclobberThis option is a useful safeguard, although it doesn't replace careful handling of important files.
3. Redirecting Standard Input
So far, we've redirected output. But commands can also read input from a file instead of the keyboard.
The < operator redirects standard input:
command < input.txtFor example, suppose names.txt contains a list of names. You can pass its contents to a command that reads from standard input:
sort < names.txtThe output is sorted and printed to the terminal. The original file is not modified.
You can also use input redirection with commands that process standard input line by line:
while IFS= read -r line; do
printf 'Name: %s\n' "$line"
done < names.txtHere, Bash reads the file through the loop's standard input.
A subtle but important point: input redirection is not the same as passing a filename as an argument.
Compare:
sort < names.txtwith:
sort names.txtBoth sort the file's contents in this example, but they work differently. In the first command, Bash opens the file and connects it to standard input. In the second, sort receives the filename as an argument and opens the file itself.
That distinction matters because some commands read from standard input when no filename is supplied, while others require a filename argument.
4. Redirecting Standard Error
Standard error is file descriptor 2. To redirect it, place 2> immediately before the destination.
command 2> errors.txtFor example:
ls /directory/that/does/not/exist 2> errors.txtThe error message is written to errors.txt instead of being displayed on the terminal.
The command's standard output remains unchanged.
You can also append errors to a file:
command 2>> errors.txtThis is particularly useful for scripts that should preserve diagnostic information across multiple runs.
Redirecting output and errors to different files
Suppose you want to save normal output and error messages separately:
find /etc -type f -print > files.txt 2> errors.txtThe results are:
files.txtcontains the paths printed byfind.errors.txtcontains diagnostic messages written to standard error.
This is often a good approach for long-running jobs because you can inspect the results and investigate failures independently.
One detail worth remembering: redirection doesn't change whether a command succeeds or fails. A command can still return a nonzero exit status even when its error messages have been redirected to a file.
5. Redirecting Both Standard Output and Standard Error
Often, you want to capture everything a command writes to standard output and standard error.
Bash provides several ways to do this.
Method 1: Redirect standard output, then duplicate it for standard error
command > output.log 2>&1This is one of the most important redirection patterns in Bash.
Let's break it down:
> output.logconnects standard output, descriptor1, tooutput.log.2>&1makes standard error, descriptor2, refer to the same destination as standard output currently uses.
Both streams therefore go to output.log.
For example:
find /etc -type f -print > find.log 2>&1Both the list of files and any diagnostic messages are written to the same log.
The order is essential. We'll explore why in the next section.
Method 2: Use &>
Bash also supports a shorter form:
command &> output.logThis redirects both standard output and standard error to the same file, overwriting it if it already exists.
To append both streams instead, use:
command &>> output.logThese forms are concise and easy to read in Bash scripts.
Portability note: &> and &>> are Bash features and aren't supported identically by every shell. If your script must run in other POSIX-compatible shells, prefer portable redirection forms such as > output.log 2>&1.
Method 3: Use >& file
Bash also accepts:
command >& output.logWhen the word after >& is a filename rather than a file descriptor, Bash treats this as a redirection of both standard output and standard error to that file.
Although this syntax is valid in Bash, &> output.log is generally easier to recognize as the Bash-specific form for redirecting both streams.
For scripts intended to be portable, use > output.log 2>&1.
6. Why the Order of Redirections Matters
This is where file descriptors become especially interesting.
Consider these two commands:
command > output.log 2>&1command 2>&1 > output.logThey may look almost identical, but they do different things.
To understand why, remember this rule:
Bash processes redirections from left to right.
Each redirection changes where a file descriptor points. A later redirection uses the configuration that exists at that moment; it doesn't automatically follow changes made to another descriptor afterward.
First command: > output.log 2>&1
command > output.log 2>&1Bash performs the following steps:
- Redirect standard output to
output.log. - Make standard error point to the same destination as standard output now uses.
Both streams go to the file.
Second command: 2>&1 > output.log
command 2>&1 > output.logThis time, Bash performs the steps in the opposite order:
- Make standard error point to the destination standard output currently uses — typically the terminal.
- Redirect standard output to
output.log.
The result is:
- Standard output goes to
output.log. - Standard error remains connected to the original destination, usually the terminal.
The second command does not mean “make standard error follow standard output forever.” The 2>&1 operation duplicates the destination currently associated with descriptor 1; it doesn't create a permanent link between the descriptors.
This principle is fundamental to understanding Bash redirection. Whenever you see multiple redirections on one command, read them from left to right.
7. Closing a File Descriptor
Sometimes you want a command to have no access to a particular stream.
You can close standard output with:
command 1>&-Or close standard error with:
command 2>&-The >&- syntax closes the specified output descriptor.
For example:
find /etc -type f -print 1>&-This closes standard output for the command. The command may encounter a write error when it attempts to print results, so closing a descriptor isn't necessarily the same as discarding output safely.
If your goal is simply to discard normal output, redirect it to /dev/null instead:
command > /dev/nullTo discard standard error:
command 2> /dev/nullTo discard both:
command > /dev/null 2>&1/dev/null is a special device that discards data written to it. It is usually the right choice when you don't need a command's output.
8. Using Additional File Descriptors
Descriptors 0, 1, and 2 are standard, but they're not the only descriptors a process can use.
You can open additional descriptors for your own purposes. This can be useful when a script needs to keep a log file open, preserve the terminal for messages, or read from multiple sources.
For example, the following command opens output.log for writing on descriptor 3:
command 3> output.logHowever, simply opening descriptor 3 doesn't redirect standard output to it. To use it, the command or script must explicitly read from or write to that descriptor.
In Bash, you can write to descriptor 3 using:
printf 'This goes to descriptor 3\n' >&3For a complete example:
{
printf 'Normal output\n'
printf 'Log message\n' >&3
} 3> output.logThe result is:
Normal outputappears on standard output, normally the terminal.Log messageis written tooutput.logthrough descriptor3.
This is one way to separate a script's ordinary output from a dedicated log stream.
Duplicating a descriptor
The general form for duplicating an output descriptor is:
n>&mThis makes descriptor n refer to the same destination as descriptor m currently uses.
For example:
command 3>&1This makes descriptor 3 point to the same destination as standard output at that moment.
For input descriptors, use <&:
command 3<&0This makes descriptor 3 refer to the same input source as standard input.
These operations duplicate an existing descriptor; they don't open a new file by themselves.
Bash also supports moving a descriptor, using syntax such as 3>&1-. This duplicates descriptor 1 onto descriptor 3 and then closes descriptor 1. Such forms are more advanced and should be used only when you specifically need to manage descriptor ownership.
9. Redirecting Output Through a Pipeline
A pipe (|) connects the standard output of one command to the standard input of another.
For example:
printf '%s\n' apple orange banana | sortThe output of printf becomes the input to sort, which prints the sorted result:
apple
banana
orangePipelines are powerful because they allow small commands to be combined into more complex operations.
But there is an important detail: a normal pipeline connects standard output, not standard error.
Consider:
find /etc -type f 2> errors.log | sortHere:
- Standard output from
findis sent tosort. - Standard error from
findis written toerrors.log. - Standard output from
sortgoes to the terminal unless redirected elsewhere.
Sending both output streams into a pipeline
If you want standard output and standard error to travel through the same pipeline, you can write:
command 2>&1 | another_commandBash redirects standard error to the current standard output destination before connecting the pipeline. Both streams then go into another_command.
Bash also provides the shorthand operator |&:
command |& another_commandThis sends both standard output and standard error through the pipe. In Bash, the implicit standard-error redirection is performed after any explicit redirections on the command.
For example:
find /etc -type f |& tee output.logBoth streams are sent through tee, which writes them to the terminal and to output.log.
Be aware that combining the streams means normal output and error messages can become interleaved. If you need to preserve the distinction between them, redirect them separately instead.
10. Using tee to Save Output and Still See It
Normally, redirecting output to a file means you no longer see it on the terminal.
But sometimes you want both: save the output for later and watch it as the command runs.
That's what tee does.
command | tee output.logtee reads from standard input and writes the data both to standard output and to the specified file.
For example:
printf 'Starting deployment\n' | tee deployment.logThe message appears on the terminal and is written to deployment.log.
To append instead of overwriting the log, use tee -a:
printf 'Deployment finished\n' | tee -a deployment.logCapturing errors with tee
A normal pipeline sends only standard output through the pipe. To capture errors too, redirect standard error to standard output:
command 2>&1 | tee output.logOr use Bash's |& shorthand:
command |& tee output.logBoth let you see the combined output while saving it to a file.
One additional consideration: by default, the exit status of a pipeline is usually the exit status of its last command. If you want Bash to report a failure from command rather than just the status of tee, consider enabling pipefail:
set -o pipefail
command 2>&1 | tee output.logWith pipefail enabled, the pipeline returns a nonzero status if a command in the pipeline fails, subject to Bash's pipeline status rules.
11. Redirecting Input and Output Together
You can combine input and output redirections in the same command.
For example:
sort < unsorted.txt > sorted.txtBash redirects standard input from unsorted.txt and standard output to sorted.txt.
The command reads the original data and writes the sorted result to a separate file.
You can also redirect standard error independently:
sort < unsorted.txt > sorted.txt 2> errors.logThis is a useful pattern for scripts that process input files and need to keep diagnostic messages separate from the results.
The general rule remains the same: each redirection configures a particular file descriptor, and Bash processes the redirections from left to right.
12. Redirection for an Entire Block of Commands
Redirections don't have to be attached to individual commands. Bash lets you apply them to a group of commands enclosed in braces or parentheses.
For example:
{
printf 'Starting task\n'
date
printf 'Task finished\n'
} > task.log 2>&1All three commands run with standard output and standard error directed to task.log.
This is convenient when a script performs several related operations and you want to capture all their output in one place.
You can also redirect a block's input:
{
read -r first
read -r second
printf 'First: %s\nSecond: %s\n' "$first" "$second"
} < input.txtThe commands in the block share the redirected standard input.
Braces run the commands in the current shell context, whereas parentheses run them in a subshell. That distinction matters when the commands change shell variables or other shell state, although the basic redirection syntax works with both forms.
13. Keeping Redirections Open with exec
So far, most examples have applied redirection to a single command or group of commands. Bash's exec builtin allows you to change the current shell's file descriptors so that later commands inherit the new configuration.
For example:
exec >> script.log 2>&1After this command, subsequent standard output and standard error from the current shell and its commands will normally be appended to script.log.
This can be useful for scripts that need to send all subsequent output to a log file without repeating the redirections on every command.
Use it carefully: the redirection persists in the current shell. If you run it interactively, your terminal may stop showing ordinary output from that shell.
You can also use exec to open a dedicated descriptor:
exec 3>> script.log
printf 'This is a log entry\n' >&3Descriptor 3 remains open for subsequent commands in the current shell until you close it:
exec 3>&-This technique is useful for separating log messages from standard output and standard error in larger scripts.
14. A Practical Logging Pattern
Let's combine several concepts into a small example.
Suppose a script needs to:
- Write normal output to a log file.
- Write errors to the same log file.
- Continue displaying progress on the terminal.
You could use tee for the combined output:
#!/usr/bin/env bash
set -o pipefail
run_task() {
printf 'Starting task...\n'
find /etc -type f
printf 'Task finished.\n'
}
run_task 2>&1 | tee task.log
status=$?
if (( status != 0 )); then
printf 'Task failed with status %s\n' "$status" >&2
exit "$status"
fiIn this example, run_task sends both output streams into the pipeline. tee displays the output and writes it to task.log. The pipefail option allows the pipeline to report a failure from run_task, even if tee succeeds.
Notice that the script captures $? immediately after the pipeline. Any intervening command would change that value.
For production scripts, you would also want to consider what happens if the log file cannot be opened or if the filesystem runs out of space. Redirection is a powerful mechanism, but it doesn't guarantee that logging will succeed.
15. Common Redirection Mistakes
A few common mistakes account for much of the confusion around file descriptors.
Mistake 1: Reversing the order of 2>&1
These commands are not equivalent:
command > output.log 2>&1command 2>&1 > output.logThe first sends both streams to the file. The second sends standard output to the file but leaves standard error connected to the original standard output destination.
Always read redirections from left to right.
Mistake 2: Thinking > redirects errors too
This command:
command > output.logredirects standard output only.
To redirect standard error as well, use:
command > output.log 2>&1Mistake 3: Using the wrong syntax
To redirect standard error to a file, write:
command 2> errors.logThe 2 is the file descriptor number and must appear before the redirection operator. 2&>file is not the correct syntax for this operation.
Mistake 4: Accidentally truncating a file
This:
command > output.logoverwrites the file's contents.
If you want to append, use:
command >> output.logMistake 5: Assuming jobs or a pipeline changes file descriptors permanently
A redirection normally applies to the command or command group where it appears. A pipeline connects streams between commands. Neither operation automatically changes the persistent descriptors of your interactive shell.
To change the current shell's descriptors for subsequent commands, use exec deliberately.
Conclusion
File descriptors are the mechanism Bash uses to connect commands to their input and output. Once you understand the three standard descriptors — stdin (0), stdout (1), and stderr (2) — the rest of redirection becomes much easier to reason about.
The most important patterns to remember are:
command > output.log
command >> output.log
command 2> errors.log
command > output.log 2>&1
command 2>&1 | tee output.logAnd above all, remember that redirections are processed from left to right. Understanding that single rule will help you predict where data goes, diagnose confusing behavior, and write more reliable Bash scripts.
File descriptors may look like a small detail, but they're one of the foundations of effective shell programming. Once they click, you can connect commands, handle errors, build logging systems, and control data flow with far more confidence.