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
readand 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).-ris almost always correct: it stops backslashes from being treated specially. Makeread -ra 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
- Bash Manual: Redirections
- TLDP — I/O Redirection (Advanced Bash-Scripting Guide)
- TLDP — Exit Codes With Special Meanings
- The
readbuiltin (Bash Manual) - Here-documents (TLDP Advanced Bash-Scripting Guide)
Search terms
linux stdin stdout stderr explainedbash redirect stderr to file 2>&1bash exit code $? meaningbash read command -r -p promptbash heredoc EOF tutoriallinux pipe command output to another command
Check yourself
- Name the three standard streams and their file descriptor numbers.
- What is the difference between
> fileand>> file? - What does
command > out.txt 2>&1do, and why must2>&1come last? - By convention, what exit code means success, and how do you read the last command's exit code?
- Write a here-doc that writes two lines to a file called
notes.txt.