Pipes and grep
The pipe operator | connects the stdout of one process to the stdin of the next, running both simultaneously. grep filters lines by a pattern — matching lines pass through, non-matching lines are discarded.
You’ve learned that commands read from stdin and write to stdout. A pipe is the mechanism that lets you connect those two channels: the stdout of one command becomes the stdin of the next. This single idea — chaining small, focused tools together — is the Unix philosophy in action. Instead of one monolithic program that searches, filters, counts, and formats, you connect simple tools in a sequence. grep is the essential filter: given a stream of lines, it lets through only the ones matching a pattern and discards the rest.
By the end of this lesson you will be able to chain any two commands with |, use grep to filter a stream by a plain string or basic regex, and understand that the two processes run simultaneously, not sequentially.
After this lesson you can use | to connect stdout of one command to stdin of another, use grep PATTERN to filter lines containing a pattern, use grep -v to invert the filter, use grep -i for case-insensitive matching, and chain multiple pipes together.
| (pipe) connects stdout of the left command to stdin of the right command. The shell creates a kernel pipe — an in-memory buffer — and sets up the two processes so one writes to it and the other reads from it. Both processes run simultaneously (in parallel), not one after the other. The writer blocks if the buffer fills up; the reader blocks if the buffer empties. The kernel coordinates them.
ls /etc | lessls /etc writes filenames to the pipe; less reads from the pipe and lets you scroll. Neither command knows about the other — ls just writes to stdout (which happens to be the pipe), and less just reads from stdin (which happens to be the pipe).
grep PATTERN prints only lines that contain PATTERN. It reads from stdin (or a file) line by line. If a line contains the pattern, it passes through to stdout. If it doesn’t match, it is silently dropped.
cat /etc/passwd | grep bashOutput: only lines in /etc/passwd that contain the string “bash”. Those are the users whose shell is /bin/bash.
Without a pipe, grep accepts a filename:
grep bash /etc/passwdSame result. The pipe form is more useful when the input itself comes from another command.
grep -v inverts the match — prints lines that do NOT match. This is the “filter out” variant.
cat /etc/passwd | grep -v nologinEvery line that does NOT contain “nologin” passes through. This is a quick way to strip comment lines, service accounts, or known noise from a stream.
grep -i makes the match case-insensitive.
ps aux | grep -i nginxMatches “nginx”, “Nginx”, “NGINX” — any capitalisation.
Both flags can combine: grep -iv pattern — case-insensitive and inverted.
You can chain multiple pipes. Each | adds another stage. The output of stage N becomes the input to stage N+1.
ps aux | grep nginx | grep -v grepStage 1: ps aux lists all running processes.
Stage 2: grep nginx keeps only lines mentioning nginx.
Stage 3: grep -v grep removes the line that mentions “grep” itself (the grep process appears in its own output, which is usually noise).
This is the core pattern: start with a producer, filter down with one or more grep stages, then pipe into a consumer.
grep supports basic regular expressions. A plain string like bash matches literally. A regex like ^root matches lines that start with “root” (^ anchors to line start). root$ matches lines ending with “root” ($ anchors to line end). The dot . matches any single character. Use -E (or egrep) for extended regex with +, ?, |, ().
grep '^root' /etc/passwdMatches only the line(s) whose first character is ‘r’, second is ‘o’, third is ‘o’, fourth is ‘t’ — the root user line.
For day-to-day filtering, plain strings are enough 90% of the time. Regex becomes important when you need anchors or alternation.
Find all processes listening on port 80.
ss -tlnp | grep ':80'ss -tlnp lists TCP listening sockets with process info. grep ':80' keeps only lines that contain “:80” — the lines for port 80. The result is a concise answer to “what is on port 80?” without manually scanning hundreds of lines.
Add a second filter to narrow further — only lines not from root:
ss -tlnp | grep ':80' | grep -v 'root'Now extend with a case-insensitive search for any web server:
ps aux | grep -i 'nginx\|apache\|caddy'\| in basic regex means “or”. This matches any of the three web server names. The pipeline pattern scales: every additional | adds one focused filter.
▸Why this works
On macOS, grep is BSD grep, which is slightly different from GNU grep (the Linux default). The flags -v, -i, -E work identically. The main difference: BSD grep’s -P (Perl-compatible regex) is not available on older macOS versions. For portable scripts, use -E for extended regex instead of -P. All examples in this lesson use only basic or extended patterns that work on both.
▸Common mistake
grep exits with a non-zero status when no lines match. In a pipeline inside a set -e script, a grep that finds nothing kills the script. The fix is to add || true after the grep when “no match” is a valid outcome: some_command | grep pattern || true. This tells the shell “exit 1 from grep is acceptable here.”
You run: ps aux | grep python | grep -v grep. What does the final grep -v grep stage do?
The pipe operator | connects the stdout of one command to the stdin of the next, with both processes running simultaneously via a kernel buffer. grep PATTERN is a line filter: matching lines pass through, non-matching lines are dropped. grep -v inverts the match; grep -i ignores case. You can chain any number of pipes to build multi-stage text processing pipelines. Each stage is a focused tool doing one thing; the pipe is the connective tissue. This is the Unix philosophy: small programs, composed by pipes, doing more together than any one of them can alone.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.