I composed single-line data pipelines using redirection, pipes, and tee to filter, transform, and log output in one pass. Understanding the three standard streams let me capture stderr separately and split a pipeline to both a file and the screen.

Objective & Context

The Unix philosophy is small tools composed via streams. This lab masters file descriptors 0/1/2, pipe composition, and tee for branching, the glue behind every filter and scripting lab.

Learning Objectives

  • Distinguish file descriptors 0 (stdin), 1 (stdout), and 2 (stderr).
  • Redirect stdout and stderr to separate files or merge them.
  • Compose multi-tool pipelines where each stage transforms the stream.
  • Branch a stream to both a file and the next pipe stage with tee.
  • Use process substitution and here-strings where pipes fall short.

Environment & Prerequisites

Prerequisites

  • Any Bash shell with GNU coreutils (standard on all Linux distributions).
  • A command that writes to both stdout and stderr (make, gcc, or curl -v).
  • A log destination file for tee practice.
  • No extra packages needed.

Step-by-Step Execution

1. Separate stdout and stderr

make 2> build-errors.log 1> build-out.log

Expected output:

(no terminal output; both files created)
$ wc -l build-out.log build-errors.log
  42 build-out.log
   3 build-errors.log

What this teaches: 2> captures only the error stream while 1> captures normal output; separating them keeps error triage clean. Order matters: 2>&1 must come after the stdout redirect to merge correctly.

2. Filter through a pipe

journalctl -u nginx | grep -i error | tail -20

Expected output:

Jun 17 09:31:02 srv01 nginx[812]: [error] 1042#0: upstream timed out
(only the last 20 error lines shown)

What this teaches: Each pipe stage filters the stream further: journalctl emits, grep keeps error lines, tail windows the last 20. Data flows left to right with no temporary files.

3. Split output with tee

dmesg | tee dmesg.log | grep -i fail

Expected output:

[  3.21] usb 1-2: device descriptor read/64, error -71

What this teaches: tee duplicates stdin to a file and stdout simultaneously, so you archive the raw dmesg output while grep shows only failures live. Add -a to append instead of overwriting.

4. Feed two live streams to one command with process substitution

diff <(sort hosts-a.txt) <(sort hosts-b.txt)

Expected output:

2d1
< web02

What this teaches: <(...) runs a command and presents its output as a readable file path (a /dev/fd entry), so tools like diff that demand two named file arguments can consume live command output. This covers the case pipes cannot: a command that needs two inputs at once. The >(...) form does the reverse, presenting a writable path.

How It Works

Every Linux process starts with three open streams: stdin (0) for input, stdout (1) for normal output, and stderr (2) for diagnostics. Redirection operators rebind these numbers to files before the command runs, which is why 2>file captures errors while leaving normal output on the terminal. The shell performs redirection itself; the command never knows its output went to a file.

A pipeline connects stdout of one process directly to stdin of the next through a kernel pipe buffer, so data streams through without touching disk. This is what makes long chains fast: each stage processes lines as they arrive. Merging with 2>&1 duplicates descriptor 2 onto wherever descriptor 1 currently points, which is why the ordering of redirections changes the result.

tee exists for the cases where you want both persistence and live processing: it reads stdin once and writes it to every named file plus stdout. Combined with process substitution, >(cmd), you can even fan one stream out to several processing commands at once, turning a linear pipeline into a small dataflow graph.

Validation & Testing

Run a command that emits both streams and confirm errors land in their own file while stdout is filtered. Pass criteria: stderr captured separately, the pipeline filters correctly, and tee writes the full stream to a file while passing it onward.

Troubleshooting

SymptomLikely causeFix
2>&1 produced an empty fileThe merge was placed before the stdout redirectWrite cmd >file 2>&1 so stderr duplicates the already-redirected stdout
Pipeline shows output but the file is emptyBuffering: the writer holds data until its buffer fills or it exitsUse stdbuf -o0 or the tool's --line-buffered flag for live streams
tee overwrote yesterday's logtee truncates by default like >Add -a to append instead of overwriting
sudo cmd > /root/file gives permission deniedRedirection runs as your user, before sudo elevatesUse sudo tee: cmd | sudo tee /root/file >/dev/null
Pipe to head kills the producer with SIGPIPEhead exits early and the writer gets a broken pipe signalUsually harmless; add 2>/dev/null if the noise bothers you
Here-string adds an unwanted trailing newline<<< always appends a newline to the wordUse printf without \n piped in, or strip it downstream
Advanced: Troubleshooting
  • Errors not captured: redirect 2> explicitly; pipes only carry stdout by default.
  • Both streams merged: use 2>&1 after the stdout redirect, order matters.
  • tee not writing: add -a to append rather than overwrite.

Key Results

  • Captured stdout and stderr to separate files in one command.
  • Built filter pipelines that reduce noisy logs to relevant lines.
  • Branched output to file and screen simultaneously with tee.
  • Internalized the three standard streams for reliable scripting.

Key Takeaways

  • Descriptors 0, 1, 2 are stdin, stdout, stderr; redirection rebinds them before the command runs.
  • Separate streams for triage; merge with >file 2>&1 in that order when you want everything.
  • Pipelines stream through kernel buffers with no temporary files.
  • tee archives and displays simultaneously; -a appends.
  • Redirection happens as your user, so sudo needs tee for privileged files.

Practice Challenges

Challenge 1: Silent build log

Goal: run a build so normal output goes to build-out.log, errors go to build-errors.log, and your terminal shows only a one-line summary.

Hint: redirect both streams, then echo based on the exit code.

Challenge 2: Fan-out monitoring

Goal: send one journalctl stream to three places at once: a full archive file, a live error-only view, and a count of critical lines.

Hint: combine tee with process substitution >( ).