Bash Scripting 16 ๐ง I/O Redirection โ Managing standard input, output, and error streams
Every process that the shell starts has three default streams: standard input (stdin), standard output (stdout), and standard error (stderr). The previous chapters covered variables, loops, and functions; this chapter covers the mechanism that connects those streams to files, pipes, and other descriptors. The shell does not pass the terminal directly to the process. It passes file descriptors 0, 1, and 2, and the process reads from 0, writes to 1, and writes its errors to 2 . The redirection operators change where those descriptors point, and the choice of operator determines whether the file is truncated, appended, or opened for reading.
Redirection is the mechanism that makes the Unix philosophy of small, composable tools practical. A command that writes to stdout can be redirected to a file, piped to another command, or discarded. A command that reads from stdin can be fed from a file, a here-document, or a here-string. The 2>&1 idiom, the &> operator, and the file-descriptor duplication are the patterns that every script needs, and the order in which the redirections are written determines the result.
Key point: The file descriptors are 0 for stdin, 1 for stdout, and 2 for stderr. The > operator truncates the file and redirects stdout, and the >> operator appends. The 2> operator redirects stderr, and the &> operator redirects both. The 2>&1 idiom duplicates stderr to the current destination of stdout, and its position relative to the > matters: cmd > file 2>&1 sends both streams to the file, while cmd 2>&1 > file sends stderr to the terminal and stdout to the file.
Why redirection requires understanding file descriptors
The truncation problem.
The > operator opens the file for writing and truncates it before the command runs. A script that uses > to write a log file erases the previous log every time it runs. The >> operator opens the file for appending instead, and the previous contents are preserved . The choice between > and >> is the difference between a log file and a scratch file, and it is a common source of data loss when the wrong operator is used.
The stream-separation problem.
The stdout and stderr are separate streams, and they are usually both connected to the terminal. A script that redirects only stdout to a file still prints the errors to the terminal, and a script that redirects only stderr still prints the normal output. The &> operator redirects both, and the 2>&1 idiom merges stderr into stdout . The order of the operators matters because the shell processes the redirections from left to right.
The descriptor-duplication problem.
The 2>&1 idiom is a duplication, not a redirection to a file named 1. It tells the shell to make file descriptor 2 point to the same destination that file descriptor 1 currently points to. If the > redirection comes first, the stdout is already pointing to the file, and the duplication makes stderr point to the file as well. If the 2>&1 comes first, the stdout is still pointing to the terminal, and the duplication makes stderr point to the terminal; the subsequent > redirects only stdout . The position is the entire meaning of the idiom.
a. The output redirection operators
The output redirection operators connect the stdout or stderr to a file. The > truncates, and the >> appends.
#!/usr/bin/env bash
# Truncate the file and write stdout
echo "Hello" > output.txt
# Append to the file
echo "World" >> output.txt
# Redirect stderr to a file
ls /nonexistent 2> errors.txt
# Redirect both stdout and stderr to the same file
ls /nonexistent &> both.txt
# The equivalent with the 2>&1 idiom
ls /nonexistent > both.txt 2>&1
The > output.txt creates the file if it does not exist and truncates it if it does. The >> output.txt appends to the file . The 2> errors.txt redirects only the stderr, and the stdout still goes to the terminal. The &> both.txt is the Bash shorthand for redirecting both streams, and the > both.txt 2>&1 is the portable form . The two forms produce the same result, but the &> is not POSIX, so a script that targets /bin/sh must use the 2>&1 form.
The >| operator is the override for the noclobber option. If the set -o noclobber option is enabled, the > refuses to overwrite an existing file, and the >| forces the overwrite.
# Enable noclobber
set -o noclobber
# This fails if the file exists
echo "Hello" > output.txt
# This overwrites regardless
echo "Hello" >| output.txt
The noclobber option is a safety feature that prevents accidental overwrites, and the >| is the escape hatch when the overwrite is intentional.
b. The input redirection and here-documents
The < operator redirects stdin from a file. The << operator is the here-document, and the <<< operator is the here-string.
#!/usr/bin/env bash
# Read stdin from a file
while IFS= read -r line; do
echo "$line"
done < input.txt
# Here-document: the content is the stdin
cat <<EOF
Line one
Line two
EOF
# Here-string: the string is the stdin
cat <<< "Hello, world"
# Here-document with expansion disabled
cat <<'EOF'
$HOME is not expanded
EOF
The < input.txt redirects the file into the loop, which is the line-reading pattern from the while loop chapter . The <<EOF here-document passes the lines between the delimiter and the EOF marker as the stdin. The <<< "Hello" here-string passes the string as the stdin, with a trailing newline . The quoted delimiter <<'EOF' disables the variable expansion, which is useful when the content contains $ characters that should be literal.
The here-document delimiter can be any word, and the <<- form strips the leading tabs from the content, which allows the content to be indented for readability.
# The <<- form strips leading tabs
if true; then
cat <<-EOF
Indented content
EOF
fi
The <<- form is the mechanism for indenting the here-document inside a function or a conditional block without the indentation becoming part of the content. The delimiter must still be on a line by itself, but the leading tabs are stripped.
c. The file-descriptor duplication and the custom descriptors
The >& operator duplicates a file descriptor, and the <& operator duplicates it for input. The exec command can open a custom descriptor that persists for the rest of the script.
#!/usr/bin/env bash
# Duplicate stdout to stderr
echo "Error" >&2
# Duplicate stderr to stdout
command 2>&1
# Open file descriptor 3 for reading
exec 3< input.txt
while IFS= read -r -u 3 line; do
echo "$line"
done
exec 3<&-
# Open file descriptor 4 for writing
exec 4> output.txt
echo "Hello" >&4
exec 4>&-
The >&2 sends the output to stderr, which is the standard way to write an error message . The 2>&1 sends the stderr to stdout. The exec 3< input.txt opens the file on descriptor 3, and the read -u 3 reads from it . The exec 3<&- closes the descriptor. The exec 4> output.txt opens the file on descriptor 4 for writing, and the >&4 writes to it. The exec 4>&- closes it.
The custom descriptors are useful when the script needs to read from a file and also read from the user’s stdin, or when the script needs to write to a log file and also write to stdout. The -u 3 flag on the read command is the mechanism that reads from the custom descriptor instead of stdin.
Complete Example Session
#!/usr/bin/env bash
# ============================================
# PART 1: OUTPUT REDIRECTION
# ============================================
echo "Truncated" > /tmp/out.txt
echo "Appended" >> /tmp/out.txt
cat /tmp/out.txt
# Output: Truncated
# Appended
# ============================================
# PART 2: STDERR REDIRECTION
# ============================================
ls /nonexistent 2> /tmp/err.txt
cat /tmp/err.txt
# Output: ls: cannot access '/nonexistent': No such file or directory
# ============================================
# PART 3: BOTH STREAMS
# ============================================
ls /nonexistent &> /tmp/both.txt
cat /tmp/both.txt
# Output: the error message
# ============================================
# PART 4: THE 2>&1 IDIOM
# ============================================
ls /nonexistent > /tmp/both.txt 2>&1
cat /tmp/both.txt
# Output: the error message
# ============================================
# PART 5: INPUT REDIRECTION
# ============================================
while IFS= read -r line; do
echo "Line: $line"
done < /etc/hostname
# ============================================
# PART 6: HERE-DOCUMENT
# ============================================
cat <<EOF
Hello, $USER
Today is $(date +%F)
EOF
# ============================================
# PART 7: HERE-DOCUMENT WITHOUT EXPANSION
# ============================================
cat <<'EOF'
$HOME is not expanded
$(date) is not executed
EOF
# ============================================
# PART 8: HERE-STRING
# ============================================
grep "hello" <<< "hello world"
# Output: hello world
# ============================================
# PART 9: CUSTOM DESCRIPTORS
# ============================================
exec 3< /etc/hostname
while IFS= read -r -u 3 line; do
echo "Host: $line"
done
exec 3<&-
# ============================================
# PART 10: DISCARD OUTPUT
# ============================================
command_that_does_not_exist 2> /dev/null
echo "Exit status: $?"
# Output: Exit status: 127
The ten parts demonstrate the redirection surface: the output operators, the stderr redirection, the both-streams redirection, the 2>&1 idiom, the input redirection, the here-document, the here-document without expansion, the here-string, the custom descriptors, and the output discarding. The critical distinction is between Parts 3 and 4: the &> and the > file 2>&1 produce the same result, but the 2>&1 > file produces a different result.
Quick Reference
Redirection Operators
| Operator | Purpose | Example |
|---|---|---|
> | Truncate and redirect stdout | cmd > file |
>> | Append and redirect stdout | cmd >> file |
2> | Redirect stderr | cmd 2> file |
&> | Redirect both | cmd &> file |
2>&1 | Duplicate stderr to stdout | cmd > file 2>&1 |
> | Override noclobber | cmd >| file |
< | Redirect stdin | cmd < file |
<< | Here-document | cmd <<EOF ... EOF |
<<< | Here-string | cmd <<< "string" |
>&N | Duplicate descriptor N | cmd >&2 |
<&N | Duplicate descriptor N for input | cmd <&3 |
The Standard Descriptors
| Descriptor | Stream | Purpose |
|---|---|---|
| 0 | stdin | Input |
| 1 | stdout | Normal output |
| 2 | stderr | Error output |
The 2>&1 Order
| Order | Result |
|---|---|
cmd > file 2>&1 | Both stdout and stderr go to the file |
cmd 2>&1 > file | stderr goes to the terminal, stdout to the file |
Useful Targets
| Target | Purpose |
|---|---|
/dev/null | Discard the output |
/dev/stdin | The stdin stream |
/dev/stdout | The stdout stream |
/dev/stderr | The stderr stream |
Best Practices
โ Do This:
# Use >> for log files
echo "Log entry" >> /var/log/myscript.log # โ
# Redirect stderr to stdout after stdout
cmd > file 2>&1 # โ
# Use the here-document for multi-line content
cat <<EOF
Line one
Line two
EOF # โ
# Use the quoted delimiter for literal content
cat <<'EOF'
$HOME is not expanded
EOF # โ
# Close the custom descriptor when done
exec 3<&-
exec 4>&- # โ
# Discard the output with /dev/null
cmd > /dev/null 2>&1 # โ
โ Don’t Do This:
# Use > for a log file
echo "Log entry" > /var/log/myscript.log # Truncates the log # โ
# Put 2>&1 before the > redirection
cmd 2>&1 > file # stderr goes to the terminal # โ
# Forget the close of the custom descriptor
exec 3< file # Leaks the descriptor # โ
# Use &> in a POSIX script
cmd &> file # Not portable # โ
# Redirect to a file named "1"
cmd 2>&1 # Duplicates the descriptor, not a file named 1 # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Log file truncated | Used > instead of >> | Use >> |
| stderr still on terminal | Missing 2>&1 | Add the duplication |
| Both streams to the terminal | 2>&1 before > | Put 2>&1 after > |
| Here-document content expanded | Unquoted delimiter | Use <<'EOF' |
| Descriptor leak | Forgot exec N<&- | Close the descriptor |
Real-World Examples
1. Truncate a file
echo "Hello" > output.txt
2. Append to a file
echo "World" >> output.txt
3. Redirect stderr
cmd 2> errors.txt
4. Redirect both
cmd &> both.txt
5. The 2>&1 idiom
cmd > both.txt 2>&1
6. Input redirection
while IFS= read -r line; do echo "$line"; done < file
7. Here-document
cat <<EOF
Hello
EOF
8. Here-document without expansion
cat <<'EOF'
$HOME
EOF
9. Here-string
grep "hello" <<< "hello world"
10. Custom descriptor
exec 3< file; read -r -u 3 line; exec 3<&-
Visual
The Three Standard Streams
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ EVERY PROCESS HAS THREE STREAMS โ
โ โ
โ โโโโโโโโโโโ โ
โ โ Process โ โ
โ โโโโโโฌโโโโโ โ
โ โ โ
โ โโโ fd 0 (stdin) โโโ keyboard or file โ
โ โ โ
โ โโโ fd 1 (stdout) โโโถ terminal or file โ
โ โ โ
โ โโโ fd 2 (stderr) โโโถ terminal or file โ
โ โ
โ The redirection operators change where the descriptors โ
โ point. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The 2>&1 Order
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE ORDER OF THE REDIRECTIONS MATTERS โ
โ โ
โ cmd > file 2>&1: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ 1. stdout โ file โ โ
โ โ 2. stderr โ the current stdout (file) โ โ
โ โ โ both streams in the file โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ cmd 2>&1 > file: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ 1. stderr โ the current stdout (terminal) โ โ
โ โ 2. stdout โ file โ โ
โ โ โ stderr on the terminal, stdout in the file โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Here-Document
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE HERE-DOCUMENT PASSES THE CONTENT AS STDIN โ
โ โ
โ cat <<EOF โ
โ Line one โ
โ Line two โ
โ EOF โ
โ โ โ
โ โผ โ
โ The content between <<EOF and EOF is the stdin of cat. โ
โ โ
โ Quoted delimiter: <<'EOF' โ
โ โ no variable expansion, no command substitution โ
โ โ
โ Unquoted delimiter: <<EOF โ
โ โ variables and commands are expanded โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Standard descriptors | 0 (stdin), 1 (stdout), 2 (stderr) |
| Truncate stdout | > |
| Append stdout | >> |
| Redirect stderr | 2> |
| Redirect both | &> or > file 2>&1 |
| Duplication order | > file 2>&1 (correct), 2>&1 > file (wrong) |
| Input redirection | < |
| Here-document | <<EOF ... EOF |
| Literal here-document | <<'EOF' ... EOF |
| Here-string | <<< "string" |
| Custom descriptor | exec N< file / exec N> file |
| Discard output | > /dev/null 2>&1 |
Key takeaways:
- The shell connects the process to the terminal through file descriptors 0, 1, and 2. The redirection operators change where those descriptors point, and the process is unaware of the change . The
>operator truncates, the>>operator appends, and the2>operator redirects stderr. - The
2>&1idiom is a duplication, and its position matters. The> file 2>&1sends both streams to the file, and the2>&1 > filesends stderr to the terminal and stdout to the file. The shell processes the redirections from left to right, and the duplication uses the current destination of the descriptor . - The
&>operator is the Bash shorthand for both streams. It is not POSIX, and a script that targets/bin/shmust use the> file 2>&1form. The&>and the2>&1produce the same result, but the&>is shorter. - The
<operator redirects stdin from a file. Thewhile IFS= read -r line; do ...; done < filepattern is the correct way to read a file line by line, and the redirection keeps the loop in the current shell . - The here-document passes the content as stdin. The
<<EOFform expands the variables and the commands, and the<<'EOF'form disables the expansion. The<<-form strips the leading tabs, which allows the content to be indented. - The custom descriptors persist for the rest of the script. The
exec 3< fileopens the file on descriptor 3, and theread -u 3reads from it. Theexec 3<&-closes the descriptor, and the close should not be forgotten .
Remember: Redirection is the mechanism that connects the process’s streams to files, pipes, and other descriptors. The standard descriptors are 0, 1, and 2, and the operators >, >>, 2>, &>, <, <<, and <<< change where they point. The 2>&1 idiom merges stderr into stdout, and its position relative to the > determines the result. The here-document and the here-string pass the content as stdin. The custom descriptors persist for the script. The result is a script that can log its output, separate its errors, read from files, and discard the noise.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!