Skip to main content

Lesson: Streams, Redirection & Exit Codes

What you'll learn

  • The three standard streams every program has: stdin, stdout, and stderr.
  • How to redirect output to files with >, >>, 2>, and &>, and how to read input.
  • How to connect commands together with pipes |.
  • How to read and use exit codes ($?) to know whether a command succeeded.
  • How to read user input with read and embed multi-line text with here-docs.

By the end you can control where a script's input comes from and where its output and errors go — the foundation of logging and error handling.

The lesson

Every command on Linux is wired up to three streams — think of them as three pipes attached to the program. You will use these constantly in scripts to capture results, separate errors, and chain tools together.

You will run all of this on the Jumpbox (10.100.100.254, user ubuntu).

1. The three standard streams

                +---------------------+
   stdin (0) -->|     your command    |--> stdout (1)  normal output
                |                     |--> stderr (2)  error messages
                +---------------------+
  • stdin (standard input, file descriptor 0) — where a program reads input, by default your keyboard.
  • stdout (standard output, descriptor 1) — normal results, by default your screen.
  • stderr (standard error, descriptor 2) — error and diagnostic messages, also the screen by default.

Keeping normal output and errors on separate streams is deliberate: it lets you save results to a file while still seeing errors, or vice versa.

2. Redirecting output: >, >>, 2>, &>

A redirection changes where a stream goes. Send stdout to a file:

echo "line one" > out.txt     # > OVERWRITES the file (creates if missing)
echo "line two" >> out.txt    # >> APPENDS to the file
cat out.txt
# line one
# line two

> clobbers (replaces) the file every time — be careful. >> adds to the end.

Errors travel on stderr, so plain > does not capture them. Redirect stderr with 2>:

ls /does/not/exist 2> errors.txt    # error goes to the file, not the screen

Common combinations:

command > out.txt 2> err.txt    # stdout and stderr to SEPARATE files
command > all.txt 2>&1          # stderr follows stdout into the same file
command &> all.txt              # shorthand for the same thing (Bash)
command > /dev/null 2>&1        # discard everything (/dev/null is a black hole)

The order in 2>&1 matters: it means "make descriptor 2 point wherever descriptor 1 currently points," so it must come after you redirect stdout.

3. Pipes: |

A pipe connects the stdout of one command to the stdin of the next, so you can build a processing chain:

ls -l /etc | grep conf | wc -l

This lists /etc, keeps only lines containing conf, then counts them. Data flows left to right:

  ls -l /etc  --stdout-->  grep conf  --stdout-->  wc -l  --> screen

Pipes only connect stdout, not stderr. Each small tool does one job well and you compose them — the Unix philosophy.

4. Exit codes and $?

When a command finishes it returns an exit code: a number from 0 to 255. By convention 0 means success and anything non-zero means failure. This is how a script knows whether the previous step worked.

The special variable $? holds the exit code of the last command:

ls /etc > /dev/null
echo "$?"        # 0  (success)

ls /nope 2> /dev/null
echo "$?"        # 2  (failure)

You will use exit codes in the next lesson to make decisions. Your own scripts should set their exit code too, with exit:

#!/usr/bin/env bash
if [[ -f /etc/hostname ]]; then
  echo "found it"
  exit 0          # success
else
  echo "missing!" >&2   # send our error to stderr
  exit 1          # failure
fi

Note >&2 — that redirects our echo to stderr, the correct stream for error messages, so callers can separate them from real output.

5. Reading input with read

read pulls a line from stdin into a variable. Combined with -p it prompts the user:

#!/usr/bin/env bash
read -rp "What is your name? " name
echo "Hello, $name"
  • -p "..." prints a prompt (no newline).
  • -r is almost always correct: it stops backslashes from being treated specially. Make read -r a habit.

You can read several values at once; read splits on whitespace:

read -rp "Enter first and last name: " first last
echo "First: $first   Last: $last"

Because read reads from stdin, it also works with pipes and files:

while read -r line; do
  echo "got: $line"
done < out.txt

That < out.txt is input redirection — it feeds the file into the loop's stdin instead of the keyboard.

6. Here-documents

A here-document (here-doc) feeds a block of multi-line text into a command's stdin without a separate file. You pick a marker word (commonly EOF):

cat <<EOF > config.txt
host = jumpbox
user = ubuntu
date = $(date +%F)
EOF

Everything between <<EOF and the closing EOF becomes input to cat, which here writes it to config.txt. Variables and $( ) are expanded inside, as shown. If you quote the marker — <<'EOF' — expansion is turned off and the text is taken literally, which is useful when writing scripts that contain $ signs.

grep "user" <<< "user=ubuntu"

7. Putting it together

#!/usr/bin/env bash
# Save a directory listing; log any error separately.
target="/etc"
if ls -l "$target" > listing.txt 2> errors.log; then
  echo "Listing saved ($(wc -l < listing.txt) lines)"
else
  echo "ls failed, see errors.log" >&2
  exit 1
fi

This captures normal output to one file, errors to another, checks the exit code, and reports cleanly on the right streams — exactly the discipline real scripts need.

Dig deeper

Search terms

  • linux stdin stdout stderr explained
  • bash redirect stderr to file 2>&1
  • bash exit code $? meaning
  • bash read command -r -p prompt
  • bash heredoc EOF tutorial
  • linux pipe command output to another command

Check yourself

  1. Name the three standard streams and their file descriptor numbers.
  2. What is the difference between > file and >> file?
  3. What does command > out.txt 2>&1 do, and why must 2>&1 come last?
  4. By convention, what exit code means success, and how do you read the last command's exit code?
  5. Write a here-doc that writes two lines to a file called notes.txt.