| |

Bash Scripting 16 ๐Ÿง I/O Redirection โ€” Managing standard input, output, and error streams

โฑ๏ธ Estimated read: 13 min | |

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

OperatorPurposeExample
>Truncate and redirect stdoutcmd > file
>>Append and redirect stdoutcmd >> file
2>Redirect stderrcmd 2> file
&>Redirect bothcmd &> file
2>&1Duplicate stderr to stdoutcmd > file 2>&1
>Override noclobbercmd >| file
<Redirect stdincmd < file
<<Here-documentcmd <<EOF ... EOF
<<<Here-stringcmd <<< "string"
>&NDuplicate descriptor Ncmd >&2
<&NDuplicate descriptor N for inputcmd <&3

The Standard Descriptors

DescriptorStreamPurpose
0stdinInput
1stdoutNormal output
2stderrError output

The 2>&1 Order

OrderResult
cmd > file 2>&1Both stdout and stderr go to the file
cmd 2>&1 > filestderr goes to the terminal, stdout to the file

Useful Targets

TargetPurpose
/dev/nullDiscard the output
/dev/stdinThe stdin stream
/dev/stdoutThe stdout stream
/dev/stderrThe 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

PitfallWhy It HappensFix
Log file truncatedUsed > instead of >>Use >>
stderr still on terminalMissing 2>&1Add the duplication
Both streams to the terminal2>&1 before >Put 2>&1 after >
Here-document content expandedUnquoted delimiterUse <<'EOF'
Descriptor leakForgot 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

ItemValue
Standard descriptors0 (stdin), 1 (stdout), 2 (stderr)
Truncate stdout>
Append stdout>>
Redirect stderr2>
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 descriptorexec 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 the 2> operator redirects stderr.
  • The 2>&1 idiom is a duplication, and its position matters. The > file 2>&1 sends both streams to the file, and the 2>&1 > file sends 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/sh must use the > file 2>&1 form. The &> and the 2>&1 produce the same result, but the &> is shorter.
  • The < operator redirects stdin from a file. The while IFS= read -r line; do ...; done < file pattern 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 <<EOF form 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< file opens the file on descriptor 3, and the read -u 3 reads from it. The exec 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.


Was this helpful?


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!