Standard Input, Output, Error, and Exit Status
Linux commands become automation when their data, diagnostics, and success signals can be connected predictably. This lesson introduces the three standard streams and the exit-status contract that CI/CD systems depend on.
Learning objectives
By the end of this lesson
- Identify standard input, standard output, and standard error by file descriptor number and purpose.
- Redirect streams to files without confusing overwrite, append, and descriptor-duplication order.
- Build pipelines and explain which process receives which stream.
- Interpret exit status and use conditional execution without losing failure information.
- Capture command output, diagnostics, and status as incident or CI evidence.
1. Commands expose data streams and a status result
A process normally begins with three open file descriptors. They may point to a terminal, regular file, pipe, socket, or another endpoint. The shell configures these descriptors before the program starts.
flowchart LR I["fd 0: standard input"] --> P["Command process"] P --> O["fd 1: standard output"] P --> E["fd 2: standard error"] P --> S["Exit status 0 to 255"] O --> N["Terminal, file, or pipe"] E --> D["Terminal, file, or log"]
0stdinInput data consumed by the process1stdoutNormal data or results produced by the process2stderrDiagnostics, warnings, and error messages2. Observe stdout, stderr, and status separately
demo_streams() {
printf 'normal-result\n'
printf 'diagnostic-message\n' >&2
return 7
}
demo_streams
status=$?
printf 'captured status=%d\n' "$status"
The function writes one line to descriptor 1, another to descriptor 2, and returns status 7. On a terminal both lines may look similar, but redirection can separate them. Always capture $? immediately; another command replaces it.
3. Redirect input and output
> fileSend stdout to a file, truncating or creating itCan destroy previous contents>> fileAppend stdout to a fileRepeated runs accumulate data2> fileSend stderr to a fileNormal stdout remains unchanged< fileUse a file as stdinThe command reads from the file rather than terminal input2>&1Make stderr point to stdout’s current destinationOrdering matters&> fileBash shorthand sending stdout and stderr to one fileNot portable to every /bin/shlab="$HOME/devops-academy/linux/chapter03/lesson05/redirection"
mkdir -p -- "$lab"
printf 'first\n' > "$lab/output.txt"
printf 'second\n' >> "$lab/output.txt"
# Separate normal output and diagnostics.
{
printf 'result\n'
printf 'warning\n' >&2
} > "$lab/stdout.log" 2> "$lab/stderr.log"
# Feed a file to standard input.
wc -l < "$lab/output.txt"
4. Redirections are processed from left to right
# Both stdout and stderr end in combined.log.
command >combined.log 2>&1
# stderr is duplicated to the original stdout first; only stdout then moves.
# The two streams are NOT combined in combined.log.
command 2>&1 >combined.log
Descriptor duplication copies a destination at that point in the redirection sequence. This distinction is critical in CI logs, service wrappers, and incident capture.
2>/dev/null can hide the evidence needed to detect permission errors, missing files, deprecations, and partial failure. Suppress only an expected and handled diagnostic.
5. Pipelines connect stdout to stdin
printf '%s\n' worker api worker scheduler api \
| sort \
| uniq -c \
| sort -nr
The shell creates pipes and starts the component commands. Standard output from one command becomes standard input to the next. Standard error is not piped unless redirected explicitly.
stdout should be parseable
Commands designed for automation should keep normal data on stdout and diagnostics on stderr.
Pipes are bounded buffers
Fast producers can block while consumers catch up. A pipeline is concurrent, not necessarily a fully buffered sequence.
Default status is incomplete
Without pipefail, a pipeline’s status is normally the last command’s status.
Variable changes may not persist
Pipeline components often execute in subshell environments. Avoid depending on side effects in the current shell.
6. Exit status is the automation contract
By convention, status 0 means success and a nonzero value indicates some kind of failure or alternate condition defined by the command. Shell built-ins, external programs, functions, and pipelines all produce a status.
if grep -q -- '^enabled=true$' app.env; then
printf 'feature enabled\n'
else
status=$?
case "$status" in
1) printf 'feature not enabled\n' ;;
*) printf 'grep failed with status %d\n' "$status" >&2; exit "$status" ;;
esac
fi
mkdir -p build && printf 'build directory ready\n'
test -f required.conf || printf 'required.conf is missing\n' >&2
Some commands use distinct nonzero statuses for “no match” versus execution error. Do not collapse every nonzero value into the same meaning without consulting documentation.
7. Preserve pipeline failure information
set -o pipefail
if output=$(generate_report | compress_report | upload_report); then
printf 'pipeline completed\n'
else
status=$?
printf 'pipeline failed with status %d\n' "$status" >&2
printf 'component statuses: %s\n' "${PIPESTATUS[*]}" >&2
exit "$status"
fi
pipefail makes a pipeline fail when a component fails, using Bash’s documented rule for the resulting status. PIPESTATUS records component statuses, but it must be captured before another command overwrites it. Reliable shell automation will be developed fully in Chapters 14 and 15.
8. Capture results and diagnostics as evidence
run_and_capture() {
if [ "$#" -lt 2 ]; then
printf 'usage: run_and_capture NAME COMMAND [ARG...]\n' >&2
return 2
fi
name=$1
shift
evidence="$HOME/devops-academy/linux/chapter03/lesson05/evidence"
mkdir -p -- "$evidence"
stdout_file="$evidence/$name.stdout.log"
stderr_file="$evidence/$name.stderr.log"
status_file="$evidence/$name.status"
printf 'command:' > "$evidence/$name.command"
printf ' %q' "$@" >> "$evidence/$name.command"
printf '\n' >> "$evidence/$name.command"
"$@" > "$stdout_file" 2> "$stderr_file"
status=$?
printf '%d\n' "$status" > "$status_file"
return "$status"
}
run_and_capture kernel uname -a
printf 'kernel status=%d\n' "$?"
run_and_capture missing ls -ld -- /path/that/does/not/exist || true
cat "$HOME/devops-academy/linux/chapter03/lesson05/evidence/missing.stderr.log"
The %q representation is useful for shell-oriented evidence, but it is not a cryptographic audit record and must not be used to log secrets.
9. Hands-on lab: build a stream-aware diagnostic report
lab="$HOME/devops-academy/linux/chapter03/lesson05"
evidence="$lab/lab-evidence"
mkdir -p -- "$evidence"
check_target() {
target=$1
printf 'checking=%s\n' "$target"
if [ -e "$target" ]; then
stat --printf='type=%F size=%s owner=%U group=%G\n' -- "$target"
return 0
fi
printf 'target does not exist: %s\n' "$target" >&2
return 4
}
for target in /etc/os-release /path/that/does/not/exist; do
safe_name=$(printf '%s' "$target" | tr '/ ' '__')
stdout_file="$evidence/${safe_name}.out"
stderr_file="$evidence/${safe_name}.err"
status_file="$evidence/${safe_name}.status"
if check_target "$target" > "$stdout_file" 2> "$stderr_file"; then
status=0
else
status=$?
fi
printf '%d\n' "$status" > "$status_file"
done
printf '=== evidence inventory ===\n'
find "$evidence" -maxdepth 1 -type f -printf '%f\n' | sort
printf '\n=== missing-target evidence ===\n'
cat "$evidence/_path_that_does_not_exist.err"
printf 'status='
cat "$evidence/_path_that_does_not_exist.status"
Verification checklist
10. Common stream and status mistakes
Overwriting a log unintentionally
> truncates before command execution. Choose append, rotation, unique filenames, or atomic replacement deliberately.
Piping stderr accidentally or not at all
A normal pipe connects stdout only. Redirect stderr explicitly when combined processing is intended.
Checking $? too late
Every later command changes it. Capture status immediately or use the command directly in if.
Ignoring an early pipeline failure
The final filter can succeed after an upstream command fails. Use pipefail and inspect component statuses where required.
11. Knowledge check
Question 1. Which file descriptors conventionally represent stdin, stdout, and stderr?
Question 2. Why do command >file 2>&1 and command 2>&1 >file differ?
Question 3. Why can a pipeline appear successful even when an early command failed?
pipefail, the pipeline status is normally the status of its final command. A later filter can succeed despite an upstream failure.12. Chapter summary
Chapter 3 established the command-line execution environment: terminal and shell layers, working-directory navigation, pathname resolution and expansion, interactive productivity features, standard streams, and exit status. These concepts make later file, process, service, network, and automation commands observable and composable.
13. Further reading
- GNU Bash Reference Manual — redirections, pipelines, lists, exit status, functions, and shell options.
- Linux manual pages for stdin, stdout, stderr, dup2, pipe, bash, printf, and test.
- POSIX Shell Command Language — redirection, pipelines, command lists, and exit status.
- GNU Coreutils manuals — conventions for standard streams and command status.
- systemd and CI runner documentation — stdout/stderr capture and process exit-code handling.
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.