Chapter 03Lesson 05~55 minutes

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.

BeginnerStreamsHands-on lab

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.

Standard streams and process result
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"]
DescriptorConventional nameIntended role
0stdinInput data consumed by the process
1stdoutNormal data or results produced by the process
2stderrDiagnostics, warnings, and error messages

2. 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

FormEffectRisk or note
> fileSend stdout to a file, truncating or creating itCan destroy previous contents
>> fileAppend stdout to a fileRepeated runs accumulate data
2> fileSend stderr to a fileNormal stdout remains unchanged
< fileUse a file as stdinThe command reads from the file rather than terminal input
2>&1Make stderr point to stdout’s current destinationOrdering matters
&> fileBash shorthand sending stdout and stderr to one fileNot portable to every /bin/sh
lab="$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.

Do not discard diagnostics casually

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.

Data interface

stdout should be parseable

Commands designed for automation should keep normal data on stdout and diagnostics on stderr.

Backpressure

Pipes are bounded buffers

Fast producers can block while consumers catch up. A pipeline is concurrent, not necessarily a fully buffered sequence.

Failure

Default status is incomplete

Without pipefail, a pipeline’s status is normally the last command’s status.

Subshells

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?

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.

Next chapter

Files, Directories, and Links

Chapter 4 begins with controlled creation, copying, moving, and removal of filesystem objects before examining metadata, inodes, links, patterns, and temporary files.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.