Redirection and File Descriptors

Reviewed & published by Brayan K

By the end of this lesson you'll understand the three standard streams (stdin, stdout, stderr), how to send output to files with > and >>, how to read input from a file with <, how to separate and combine stdout and stderr (including the famous 2>&1 ordering trap), how to discard output with /dev/null, and how to use here-documents, here-strings, and tee.

Part of the free Command Line course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1. The Three Standard Streams

Every command is born with three open streams, each identified by a number: 0 is stdin (the input it reads), 1 is stdout (its normal output), and 2 is stderr (its error messages). By default all three are wired to your terminal, which is why normal output and errors appear mixed together — but they are genuinely separate channels, and that separation is what every redirection below exploits.

# Every command starts life with three open streams, each with a number:
#   0  stdin   — where it READS input from (default: the keyboard)
#   1  stdout  — where it WRITES normal output (default: the terminal)
#   2  stderr  — where it WRITES error messages (default: the terminal)

# Make a file that exists, so this runs the same on any machine.
printf 'my-machine\n' > hostname.txt

# 'ls' on a file that exists prints a NORMAL line on stdout (fd 1).
# 'ls' on a missing file prints an ERROR line on stderr (fd 2).
#
# Run it on both at once and the terminal blends the two, so it LOOKS
# like one stream — and which line lands first is not fixed. Send stderr
# to a file instead and the separation becomes visible AND repeatable:
ls hostname.txt /no/such/file 2> errors.txt

echo "--- everything above came from stdout (fd 1) ---"
cat errors.txt
echo "--- and that line came from stderr (fd 2) ---"

2. Sending stdout to a File: > and >>

The > operator redirects stdout into a file. It truncates the file first (emptying it, or creating it if it does not exist), so the file ends up containing only what that one command wrote. The >> operator instead appends to the end of the file, preserving what was already there — perfect for a growing log.

# >  redirects stdout (fd 1) to a file. It TRUNCATES the file first
# (or creates it if it doesn't exist) — so the file ends up holding
# ONLY what this command wrote.
echo "first line"  > log.txt
echo "second line" > log.txt   # truncates again — "first line" is gone!
cat log.txt

echo "--- now with >> (append) ---"

# >> APPENDS to the end instead of truncating. Nothing is erased.
echo "line A" >  notes.txt      # start fresh
echo "line B" >> notes.txt      # add to the end
echo "line C" >> notes.txt      # add again
cat notes.txt

3. Reading stdin from a File: <

The < operator redirects stdin so a command reads from a file instead of the keyboard. Many tools (sort, wc, grep) read whatever arrives on stdin, so sort < file sorts the file's lines. This is subtly different from passing the filename as an argument: with < the shell opens the file and hands the command an already-open input stream.

# First build a small file to read FROM.
printf 'pear\napple\ncherry\n' > fruit.txt

# <  redirects stdin (fd 0): the command reads the FILE instead of
# the keyboard. Here 'sort' reads fruit.txt and sorts the lines.
sort < fruit.txt

echo "--- count the lines the same way ---"
# wc -l counts lines on whatever arrives on stdin.
wc -l < fruit.txt

4. Errors and the 2>&1 Ordering Trap

Use 2> to redirect stderr alone. To merge the streams, 2>&1 tells the shell "send fd 2 to wherever fd 1 points right now" — the &1 means the current target of stdout, not a file named 1. Because redirections are applied left to right, command > file 2>&1 sends both to the file, but the reversed command 2>&1 > file sends only stdout to the file and leaves stderr on the terminal — the classic gotcha.

# 2>  redirects ONLY stderr (fd 2). stdout still goes to the terminal.
# Here the good filename prints on the terminal; the error goes to err.txt.
ls /etc/hostname /no/such/file 2> err.txt
echo "--- what landed in err.txt: ---"
cat err.txt

echo "--- combine streams: > file 2>&1 (BOTH to the file) ---"
# 2>&1 means "send fd 2 to wherever fd 1 points RIGHT NOW".
# Order matters: > out.txt moves stdout to the file FIRST, then 2>&1
# points stderr at that same file. So BOTH end up in out.txt.
ls /etc/hostname /no/such/file > out.txt 2>&1
cat out.txt

echo "--- the GOTCHA: 2>&1 > file (only stdout to the file) ---"
# Reversed order. 2>&1 first copies stdout's CURRENT target (still the
# terminal), THEN > only.txt moves stdout. stderr was already aimed at
# the terminal, so it prints here and only stdout lands in the file.
ls /etc/hostname /no/such/file 2>&1 > only.txt
echo "--- only.txt has stdout alone: ---"
cat only.txt

5. Discarding Output: /dev/null and &>

/dev/null is the system's discard sink: anything written to it is thrown away. Redirect to it to silence streams you do not care about — 2> /dev/null hides errors, > /dev/null hides normal output. The bash shorthand &> sends both stdout and stderr at once, so &> /dev/null runs a command completely silently (it is the same as > /dev/null 2>&1). The command still runs and still sets $?.

# /dev/null is the "black hole" — anything written to it is discarded.

# Throw away ERRORS only, keep normal output on the terminal:
ls /etc/hostname /no/such/file 2> /dev/null

echo "--- throw away normal output, keep only errors ---"
# Send stdout to the void; stderr still reaches the terminal.
ls /etc/hostname /no/such/file > /dev/null

echo "--- silence EVERYTHING with the bash shorthand &> ---"
# &> file sends BOTH stdout and stderr to one place. &> /dev/null is the
# usual way to run a command completely silently. It equals > /dev/null 2>&1.
ls /etc/hostname /no/such/file &> /dev/null
echo "exit status was: $?"   # 2 — the command still RAN and failed, just quietly

6. Here-Documents, Here-Strings, and tee

A here-document (<<EOF ... EOF) feeds several literal lines into a command as stdin — ideal for writing a config file with cat > file <<EOF. Quote the delimiter (<<'EOF') to switch off variable and command expansion. A here-string (<<< "text") feeds a single string as stdin without a temp file. And tee duplicates stdin: it writes to a file and passes the text through to stdout, with -a to append.

# A here-document feeds several fixed lines into a command as stdin,
# between <<EOF and a line that is exactly EOF. Here we write a file.
name="Ada"
cat > greeting.txt <<EOF
Hello, ${name}!
Today's shell is $0.
EOF
cat greeting.txt

echo "--- quoted delimiter <<'EOF' disables expansion ---"
# Quote the word (<<'EOF') and NOTHING is expanded — ${name} and $0
# are written literally.
cat <<'EOF'
Literal: ${name} and $0 are NOT expanded.
EOF

echo "--- here-string <<< feeds one string as stdin ---"
# <<< sends a single string straight into the command's stdin.
wc -w <<< "one two three four"

echo "--- tee writes to a file AND passes the text through ---"
# tee duplicates stdin: it writes to the file and ALSO to stdout, so the
# next stage (or your screen) still sees it. -a appends instead of truncating.
echo "saved + shown" | tee copy.txt
echo "appended too"  | tee -a copy.txt
echo "--- copy.txt now holds both lines: ---"
cat copy.txt

🎯 Your Turn: append without erasing

Fill in the one blank marked ___ using the # 👉 hint so the second line is added rather than overwriting the file, then run it and check the Output panel matches.

# 🎯 YOUR TURN — add a line WITHOUT erasing the file.

echo "log started" > app.log     # creates the file with one line

# 1) Append a second line so "log started" is KEPT.
echo "user logged in" ___ app.log   # 👉 replace ___ with  >>

cat app.log

# ✅ Expected output:
#    log started
#    user logged in

🎯 Your Turn: capture both streams

Send both the normal output and the error message into one file. Put the redirection in the correct order, fill in the blank, then run it.

# 🎯 YOUR TURN — capture BOTH stdout and stderr in one file.

# 1) Send stdout to all.txt, then point stderr at the SAME place.
ls /etc/hostname /no/such/file > all.txt ___   # 👉 replace ___ with  2>&1

echo "--- all.txt should contain the error AND the good line: ---"
cat all.txt

# ✅ Expected output:
#    --- all.txt should contain the error AND the good line: ---
#    ls: cannot access '/no/such/file': No such file or directory
#    /etc/hostname

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskOperatorNotes
Redirect stdout (truncate)cmd > fileEmpties file first (fd 1)
Append stdoutcmd >> fileKeeps existing contents
Redirect stdincmd < fileRead file as input (fd 0)
Redirect stderrcmd 2> fileErrors only (fd 2)
Merge stderr into stdoutcmd > file 2>&1Order matters; both to file
Both streams (bash)cmd &> fileSame as > file 2>&1
Discard outputcmd > /dev/null2> for errors, &> for both
Here-documentcmd <<EOF ... EOFQuote 'EOF' to stop expansion
Here-stringcmd <<< "text"One string as stdin
Save and pass throughcmd | tee file-a appends to file

Mini-Challenge: capture a build log

No blanks this time — just a brief and an outline. Combine a here-document, stream merging with the right order, /dev/null, and tee into a few commands that demonstrate everything from this lesson, and check your output against the hints in the comments.

# 🎯 MINI-CHALLENGE: a tiny build-log capture
# Write a few commands (or a small script) that:
#
# 1. Uses a here-document (<<EOF) to write three fixed lines into
#    report.txt, then cat it back.
# 2. Runs  ls realfile missingfile  and captures BOTH stdout and
#    stderr into build.log using  > build.log 2>&1  (note the ORDER).
# 3. Repeats the same ls but this time DISCARDS errors with 2> /dev/null
#    while still showing the good line on screen.
# 4. Uses  tee -a  to append a "done" line to build.log while also
#    printing it to the terminal.
#
# Hint: remember  > file 2>&1  puts both in the file, but
#       2>&1 > file  leaves stderr on the terminal.

# your commands here

🎉 Lesson Complete!

Practice quiz

Which file descriptor number is standard error (stderr)?

  • 2
  • 1
  • 3
  • 0

Answer: 2. By convention 0 is stdin, 1 is stdout, and 2 is stderr. So stderr is file descriptor 2.

What does the operator > do when you write command > file ?

  • Reads the file as stdin for the command
  • Redirects stderr into the file
  • Truncates (or creates) the file and writes stdout into it
  • Appends stdout to the end of the file

Answer: Truncates (or creates) the file and writes stdout into it. A single > redirects stdout to the file, truncating it first (or creating it if it does not exist). Use >> to append instead.

How do you APPEND stdout to an existing file without erasing its contents?

  • command < file
  • command >> file
  • command 2> file
  • command > file

Answer: command >> file. The >> operator appends stdout to the end of the file. A single > would truncate the file first.

What does 2>&1 mean?

  • Discard both stdout and stderr
  • Send stdin to stdout
  • Swap stdout and stderr permanently
  • Send file descriptor 2 (stderr) to wherever file descriptor 1 (stdout) currently points

Answer: Send file descriptor 2 (stderr) to wherever file descriptor 1 (stdout) currently points. 2>&1 makes stderr (fd 2) go to the same place stdout (fd 1) is pointing AT THAT MOMENT. The &1 means the current target of fd 1, not a file literally named 1.

Where does stderr end up in command 2>&1 > file ?

  • stdout goes to the file and stderr goes to the original terminal
  • Both go to the terminal
  • stderr goes to the file and stdout goes to the terminal
  • Both stdout and stderr go to the file

Answer: stdout goes to the file and stderr goes to the original terminal. Redirections are processed left to right. 2>&1 copies stdout's target FIRST (still the terminal), then > file moves stdout to the file. So stderr stays on the terminal and only stdout lands in the file.

Which redirection feeds the contents of a file into a command as its standard input?

  • command | file
  • command < file
  • command > file
  • command >> file

Answer: command < file. The < operator redirects stdin so the command reads from the file instead of the keyboard.

What is /dev/null used for in redirection?

  • A file that stores all errors permanently
  • The default log file for the shell
  • A device that prints to the screen
  • A discard sink: anything written to it is thrown away

Answer: A discard sink: anything written to it is thrown away. /dev/null is the null device. Writing to it discards the data, so command 2> /dev/null silently throws away error messages.

What does the bash shorthand &> do, as in command &> file ?

  • Appends stdout to the file
  • Reads the file as stdin
  • Sends BOTH stdout and stderr to the file
  • Runs the command in the background

Answer: Sends BOTH stdout and stderr to the file. In bash &> file is shorthand for sending both stdout and stderr to the file. It is equivalent to > file 2>&1.

What does a here-string command <<< "text" do?

  • Creates a multi-line here-document
  • Feeds the single string text into the command as standard input
  • Redirects stderr to a string
  • Appends text to a file

Answer: Feeds the single string text into the command as standard input. A here-string <<< feeds the given word or quoted string into the command on stdin, like a one-line here-document.

In a here-document, how do you DISABLE variable and command expansion inside the body?

  • Quote the delimiter, for example <<'EOF'
  • Add 2>&1 after the delimiter
  • Use >> instead of <<
  • Use <<- instead of <<

Answer: Quote the delimiter, for example <<'EOF'. Quoting the delimiter, as in <<'EOF', turns off all expansion so $variables and command substitutions are written literally. An unquoted <<EOF expands them.

Continue this course

Frequently asked questions

What are file descriptors 0, 1, and 2?

They are the three streams every command opens automatically. 0 is standard input (stdin), where the command reads input from. 1 is standard output (stdout), where it writes normal results. 2 is standard error (stderr), where it writes error and diagnostic messages. Keeping stdout and stderr on separate numbers is what lets you redirect normal output and errors to different places.

What is the difference between > and >> ?

Both redirect stdout to a file, but > truncates the file first (it empties it, or creates it if missing) so the file ends up holding only what the new command wrote. >> appends to the end of the file instead, preserving whatever was already there. Use > to start fresh and >> to keep adding, such as to a growing log.

What does 2>&1 actually mean, and why does order matter?

2>&1 says 'make stderr (fd 2) go to wherever stdout (fd 1) is pointing right now'. The &1 refers to the current target of fd 1, not a file named 1. Because the shell applies redirections left to right, command > file 2>&1 first moves stdout to the file and then points stderr at that same file, so BOTH land in the file. But command 2>&1 > file copies stdout's target (still the terminal) onto stderr first, then moves only stdout to the file, so stderr keeps printing to the terminal.

How do I throw away output I don't care about?

Redirect it to /dev/null, the system's discard sink. command > /dev/null drops normal output, command 2> /dev/null drops only error messages, and command &> /dev/null (bash shorthand for > /dev/null 2>&1) drops everything and runs the command completely silently. The command still runs and still sets an exit status; you just don't see its text.

When should I use a here-document, a here-string, or tee?

Use a here-document (<<EOF ... EOF) to feed several fixed lines into a command as stdin, for example to write a config file with cat > file <<EOF. Quote the delimiter as <<'EOF' to stop variable and command expansion. Use a here-string (<<< "text") to feed a single string as stdin without a temp file. Use tee when you want output written to a file AND passed through to the screen or the next command at the same time; add -a to append rather than overwrite.