Output Redirection, Append, noclobber, and tee
Output redirection is deceptively compact syntax. A single character can overwrite a file, merge diagnostics into normal output, or change which destination a descriptor references. This lesson treats redirection as production I/O wiring rather than shorthand.
Learning objectives
By the end of this lesson
- Use overwrite and append redirection deliberately.
- Redirect stdout and stderr independently or together.
- Explain why redirection order changes behavior.
- Use noclobber as a guardrail against accidental overwrite.
- Apply tee and grouped redirection to create auditable automation logs.
1. > replaces; >> appends
The most common output redirections connect stdout to a regular file. > opens the destination for writing and normally truncates an existing file. >> opens it for append.
printf 'first\n' > report.txt
printf 'second\n' > report.txt
cat report.txt
printf 'third\n' >> report.txt
cat report.txtTreat > as a state-changing operation. If the target path is wrong, existing content can be lost before the command itself does useful work.
2. Prefix the redirection with the descriptor you mean
When no descriptor is written, output redirection defaults to stdout, descriptor 1. To redirect stderr, use descriptor 2 explicitly.
command_that_might_fail() {
printf 'normal record\n'
printf 'warning record\n' >&2
}
command_that_might_fail >stdout.log 2>stderr.log
printf '%s\n' '--- stdout.log ---'
cat stdout.log
printf '%s\n' '--- stderr.log ---'
cat stderr.log>fileRedirect stdoutEquivalent to 1>file2>fileRedirect stderrKeeps stdout unchanged>>fileAppend stdoutDoes not truncate existing content2>>fileAppend stderrUseful for diagnostic logs3. Merge stderr into the current stdout destination
The form 2>&1 duplicates the destination currently used by stdout onto descriptor 2.
{
printf 'normal\n'
printf 'diagnostic\n' >&2
} >combined.log 2>&1
cat combined.logBash also supports &>combined.log as a convenient Bash-specific shorthand for redirecting both stdout and stderr to one file. The explicit form is worth learning because it exposes descriptor semantics and is more portable across shell dialects.
4. Redirections are processed from left to right
These two commands are not equivalent:
# Both streams end up in all.log:
some_command >all.log 2>&1
# stderr is duplicated to the shell's OLD stdout first;
# only stdout is then redirected to output.log:
some_command 2>&1 >output.logflowchart TB A["Start: stdout -> terminal; stderr -> terminal"] --> B[" >all.log"] B --> C["stdout -> all.log; stderr -> terminal"] C --> D[" 2>&1"] D --> E["stdout -> all.log; stderr -> all.log"]
2>&1 means “make stderr point wherever stdout points right now,” not “permanently link stderr to whatever stdout may become later.”
5. noclobber can block accidental regular-file truncation
Bash's noclobber option makes a normal > redirection fail when the destination is an existing regular file. It is a useful interactive and scripting guardrail, though it does not replace correct path validation.
set -o noclobber
printf 'protected\n' > protected.txt
# This second write fails instead of truncating the file:
printf 'replacement\n' > protected.txt || printf 'overwrite blocked\n' >&2
cat protected.txt
set +o noclobberWhen you intentionally need to override noclobber, Bash provides >|. Use that operator only when the overwrite is explicitly part of the design.
set -o noclobber
printf 'forced replacement\n' >| protected.txt
set +o noclobber
cat protected.txt6. tee copies stdin to files and stdout
tee is useful when you want to preserve a stream in a file while still allowing it to continue through the terminal or pipeline.
printf '%s\n' alpha beta gamma | tee values.log
printf '%s\n' delta | tee -a values.log
cat values.logflowchart LR C["command stdout"] --> T["tee"] T --> F["log file"] T --> O["tee stdout -> terminal or next pipe"]
Remember that tee receives only the stream connected to its stdin. stderr from the command on the left does not automatically enter the pipe.
7. Include diagnostics in tee only when that is your intention
To send both streams through tee, merge stderr into stdout before the pipe:
{
printf 'result\n'
printf 'warning\n' >&2
} 2>&1 | tee combined.logThis is useful for a human-oriented session log. It is usually a bad choice when stdout is a machine-readable API, because merging destroys the distinction between result data and diagnostics.
8. Redirect a whole command group once
Brace groups let several commands share one redirection without opening the file repeatedly.
{
printf '=== environment ===\n'
printf 'user=%s\n' "$(whoami)"
printf 'pwd=%s\n' "$PWD"
printf 'bash=%s\n' "$BASH_VERSION"
} > environment.txt
cat environment.txtThe closing brace must be followed by a command terminator such as a newline or semicolon. Brace groups run in the current shell context, unlike parenthesized subshell groups.
9. Build logs without corrupting command results
A common production pattern is to keep stdout reserved for a result and write diagnostics to a separate log file. Allocate a descriptor for logging:
log_file="build.log"
exec 3>>"$log_file"
printf 'build started\n' >&3
artifact="/tmp/app.tar.gz"
printf 'artifact=%s\n' "$artifact" >&3
# Clean machine-readable output:
printf '%s\n' "$artifact"
exec 3>&-This pattern becomes more useful later when functions, timestamps, log levels, and traps are introduced.
10. Redirection does not make multi-command updates atomic
Writing through > can truncate a file before all content is successfully generated. For important configuration files, a safer pattern is often: write a temporary file, validate it, then rename it into place.
target="generated.conf"
tmp=$(mktemp "${target}.XXXXXX")
if generate_configuration >"$tmp"; then
# validate_configuration "$tmp" would go here
mv -- "$tmp" "$target"
else
rm -f -- "$tmp"
exit 1
fiRedirection controls where bytes go; it does not by itself provide transactional file updates.
11. Hands-on lab: create separate result and session logs
Create a script whose stdout is a single artifact path while stderr is copied both to the terminal and to a persistent log.
mkdir -p "$HOME/devops-academy/bash/chapter03/lesson02"
cd "$HOME/devops-academy/bash/chapter03/lesson02"
cat > build.sh <<'EOF'
#!/usr/bin/env bash
artifact="./output/app.tar.gz"
mkdir -p ./output
printf 'preparing build directory\n' >&2
printf 'fake artifact content\n' > "$artifact"
printf 'build completed\n' >&2
printf '%s\n' "$artifact"
EOF
# Capture stdout as the result.
# Send stderr through tee, but route tee's own stdout back to stderr.
artifact=$(
bash build.sh 2> >(tee -a build.log >&2)
)
printf 'caller received artifact: %s\n' "$artifact"
printf '%s\n' '--- persistent log ---'
cat build.logVerification checklist
12. Knowledge check
Question 1. What is the key difference between > and >>?
> normally truncates the destination before writing; >> opens it for append.Question 2. Why does cmd >file 2>&1 differ from cmd 2>&1 >file?
Question 3. What does tee do?
13. Summary
Output redirection is descriptor wiring with real state-changing consequences. Use > only when replacement is intended, >> for append, descriptor numbers to preserve stream separation, and tee when one stream must continue while also being recorded. Redirection order is semantic, not stylistic.
14. Further reading
- GNU Bash Reference Manual — Redirections and the
noclobberoption. - GNU Coreutils manual —
tee. - POSIX Shell Command Language — redirection ordering.
- Linux
dup(2)manual page — descriptor duplication semantics.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.