datarekha

Pipes & Redirection

Learn how to chain small commands together using pipes and redirection — the heart of the Unix philosophy.

9 min read Intermediate Command Line Lesson 7 of 14

What you'll learn

  • The three I/O streams: stdin, stdout, and stderr
  • How to chain commands with the pipe operator
  • How to redirect output to files and discard unwanted streams

Before you start

The last lesson promised this exact idea: every command does one small thing, and the power comes from chaining them. Here is the connector that does the chaining.

You already know commands like grep, ls, and ps. The real power of the command line is not any individual tool — it is connecting them. A single pipeline can replace a custom script, and once it clicks, you will wonder how you ever worked without it.

The three I/O streams

Every process that runs on a Unix-like system has three standard streams attached to it automatically:

StreamNumberMeaning
stdin0Data flowing into the process (usually your keyboard)
stdout1Normal output flowing out (usually your terminal)
stderr2Error messages flowing out (also your terminal, but separate)

The critical insight: stdout and stderr both appear on your screen by default, but they are different channels. That distinction matters as soon as you start redirecting.

The pipe operator

The pipe | connects the stdout of one command to the stdin of the next. The second command never knows or cares where its input came from — it just reads from stdin as usual.

Here is the motivating example from the question above:

ps aux | grep python | wc -l
7

What just happened, step by step:

  1. ps aux — list every running process with details, write to stdout
  2. grep python — read that list from stdin, pass through only lines containing “python”, write matches to stdout
  3. wc -l — count the lines it receives on stdin, print the count

No temporary files. No custom script. Three independent tools, composed.

ps auxall processesstdout → stdingrep pythonfilter linesstdout → stdinwc -lcount lines
Each command reads from stdin and writes to stdout — the pipe connects them.

Redirection: sending streams to files

Instead of displaying stdout on your terminal, you can send it to a file.

Overwrite with >

ls -lh ~/Documents > file-list.txt

The file file-list.txt is created (or erased and replaced if it already exists) with the output.

Append with >>

echo "Run at $(date)" >> run-log.txt

Each execution adds a new line. Nothing is destroyed.

Feed a file into stdin with <

sort < unsorted-names.txt

sort reads the file as if you had typed its contents. This form is less common — most commands accept a filename as an argument — but it matters when a command only reads from stdin.

Redirecting stderr

Remember: errors go to stderr (stream 2), not stdout (stream 1). They will not travel down a pipe unless you redirect them.

Redirect stderr to a file with 2>

find / -name "secrets.txt" 2> errors.txt

Permission-denied messages go to errors.txt; real results still appear on your terminal.

Merge stderr into stdout with 2>&1

./build.sh > build.log 2>&1

Read this right-to-left: “redirect stderr (2) to wherever stdout (1) is currently going” — which is build.log. Now both streams land in one file.

A shorthand that means the same thing:

./build.sh &> build.log

Throw stderr away entirely

find / -name "secrets.txt" 2> /dev/null

/dev/null is a special file that discards everything written to it. Useful when you care about results but not error noise.

Stderr does not travel down a pipe by default

find / -name "*.py" | grep src

Only the found filenames (stdout) flow into grep. The flood of “Permission denied” errors go straight to your terminal on stderr. To pipe everything — results and errors alike:

find / -name "*.py" 2>&1 | grep src

A realistic multi-step example

You want to audit which Python processes are consuming the most memory, save the top 5 to a file, and discard any errors:

ps aux 2>/dev/null | grep '[p]ython' | sort -k4 -rn | head -5 > python-top.txt
shreyash  1234  2.1 12.3 python train.py
shreyash  5678  1.8  9.1 python serve.py
...

Breaking it down:

  • ps aux 2>/dev/null — list processes, discard any errors
  • grep '[p]ython' — keep Python lines (the bracket trick prevents grep itself from matching)
  • sort -k4 -rn — sort by column 4 (memory %) in reverse numeric order
  • head -5 — keep the top 5 lines
  • > python-top.txt — save the result, overwriting any previous file

Quick reference

OperatorEffect
|Pipe stdout of left command into stdin of right
>Redirect stdout to file (overwrite)
>>Redirect stdout to file (append)
<Read stdin from file
2>Redirect stderr to file
2>&1Merge stderr into stdout
&>Redirect both stdout and stderr to file
/dev/nullDiscard any stream sent here

In one breath

Every process gets three streams: stdin (0, input), stdout (1, normal output), stderr (2, errors) — and stdout and stderr both reach your screen but are separate channels. The pipe | wires one command’s stdout into the next’s stdin, so ps aux | grep python | wc -l composes three tools into one answer with no temp files. Redirection sends a stream to or from a file: > overwrites (destructive — it erases the target the instant the shell opens it), >> appends, < feeds a file into stdin. Errors need their own handling because stderr does not travel down a pipe: 2> sends it to a file, 2>&1 (or &>) merges it into stdout, and 2>/dev/null throws it away.

Practice

Before the quiz, assemble one pipeline and reason about its streams. From access.log, you want the count of lines containing 500, with any read errors discarded and the number appended to daily-counts.txt. Write the single line. Then answer: in make build > out.log, why do the compiler’s error messages still show up on your screen, and what one addition captures them into the log too?

Quick check

0/3
Q1You run `make build > build.log`. Error messages from the build appear on your terminal instead of in the log. Why?
Q2What does `cat access.log | grep '404' | wc -l` count?
Q3You have a script that runs nightly. You want to keep a running history of its output across many runs without losing previous entries. Which redirection should you use?

A question to carry forward

You can now wire tools together with | and capture the results with >. But step back and ask: what is actually flowing through those pipes? So far, mostly grep to filter and wc to count — useful, but the pipe’s real magic shows only when each stage transforms the stream. Picture a raw access log and the question “which ten IP addresses hit us most?” Answering it needs a little assembly line: slice out the IP column, sort the lines, collapse identical neighbours into counts, sort again by that count, take the top ten. Each of those is a tiny single-purpose tool — cut, sort, uniq, wc — and strung together with the pipes you just learned, they turn a four-million-line log into a ten-line leaderboard. Those building blocks are the next lesson, and they close out this chapter.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Related lessons

Explore further

Glossary terms
Cheat sheets
Skip to content