PLAINFINCH learn by building

Pipes

You will join commands with the `|` symbol so the output of one becomes the input of the next, and answer questions in one line.

What is a pipe?

A pipe is the | character between two commands. It takes everything the left command would have printed and feeds it to the right command as its input. Nothing is saved to a file in between, and nothing appears on screen until the last command prints.

Each command in the chain does one small job. grep keeps matching lines, head keeps the first few, tail keeps the last few. On their own they're modest. Joined with pipes, they answer questions like "which sourdough orders are still waiting, oldest first" in a single line.

The vertical bar sits above the backslash on most UK and US keyboards, typed with Shift. On some laptop keyboards it's printed as a broken bar.

grep sourdough orders.csv | grep waiting
1043,2026-03-10,sourdough,1,waiting
1047,2026-03-12,sourdough,2,waiting
1049,2026-03-13,sourdough,1,waiting
1051,2026-03-14,sourdough,4,waiting

How does the data flow through a pipeline?

Every command has a standard input, where it reads text from, and a standard output, where it writes results. Normally standard output goes to your screen. A pipe connects one command's standard output to the next command's standard input.

Many commands read a file if you name one and read standard input if you don't. That's why grep waiting needs no file name after a pipe: its input is already arriving from the left. Writing grep waiting orders.csv on the right side of a pipe would make grep ignore the pipe and read the file again from the start, which is a common way to get a confusing result.

Error messages travel separately, on a third stream called standard error. They go to your screen even in the middle of a pipeline, which is why you still see No such file or directory when an early stage fails. The lesson on saving output shows how to send them somewhere else.

How do you build a pipeline without getting lost?

Build it one stage at a time and look at the output after each step. Start with the first command alone. When its output looks right, press the Up arrow to bring the line back, add | and the next command, and run it again.

Working this way, each run shows you exactly what the next stage will receive. When a pipeline goes wrong you can tell which stage did it, because you saw the output before and after that stage was added. Experienced people do this all the time; long one-liners are rarely typed in one go.

Peek at the file first, too. head -n 3 orders.csv shows the header and two rows, which tells you the columns and how words are spelt before you choose a pattern.

head -n 3 orders.csv
grep sourdough orders.csv
grep sourdough orders.csv | grep waiting
grep sourdough orders.csv | grep waiting | head -n 2
FigureA pipeline as four boxes. orders.csv has 13 lines. grep sourdough passes 6, grep waiting passes 4, and head -n 2 passes 2: orders 1043 and 1047, the oldest waiting sourdough.A pipeline as four boxes. orders.csv has 13 lines. grep sourdough passes 6, grep waiting passes 4, and head -n 2 passes 2: orders 1043 and 1047, the oldest waiting sourdough.
Each | hands the lines that survived one stage to the next. Building it a stage at a time lets you watch the count shrink from 13 to 2.

How do head and tail behave in a pipe?

head -n 2 keeps the first two lines of whatever reaches it and discards the rest. tail -n 1 keeps the last line. In a pipe they act on the filtered list, which may be very different from the file.

The bakery's file ends with order 1052, a collected cinnamon bun order. tail -n 1 orders.csv prints that line. The newest order that is still waiting is 1051, and you only get it by filtering first:

tail -n +2 is the other trick to know: it starts printing at line 2, so it drops a header row. Put it at the front of a pipeline when the first line of a CSV file would get in the way.

tail -n 1 orders.csv
1052,2026-03-14,cinnamon bun,12,collected

grep waiting orders.csv | tail -n 1
1051,2026-03-14,sourdough,4,waiting

Why does the order of the stages change the answer?

Each stage only sees what the previous one passed along. head -n 3 orders.csv | grep rye takes the first three lines of the file (the header and two orders) and then looks for rye among just those. It finds one order. grep rye orders.csv | head -n 3 finds every rye order first and then keeps three of them.

A good rule: filter first, then cut. Put the commands that choose lines (grep) before the commands that trim the list (head, tail). Reverse that order only when you mean "look in the first part of the file".

head -n 3 orders.csv | grep rye
1042,2026-03-09,rye,1,collected

grep rye orders.csv | head -n 3
1042,2026-03-09,rye,1,collected
1046,2026-03-11,seeded rye,2,waiting
1050,2026-03-13,rye,2,waiting

Is cat file | grep wrong?

It works. cat orders.csv | grep waiting prints the same lines as grep waiting orders.csv. People sometimes call the first form a "useless use of cat", because grep can open the file itself and the extra command does nothing.

Prefer giving the file to the first real command. It's shorter, and grep can then label lines with the file name when you search several files. That said, starting with cat while you're exploring is harmless, and some people find a pipeline that reads left to right from cat easier to edit.

What can go wrong when a stage finds nothing?

If an early stage outputs nothing, every later stage receives nothing and the whole pipeline prints nothing at all. No error appears, because nothing failed: grep found no matches and the next stage had nothing to work on.

When a pipeline is silent, remove stages from the end until you find the one that empties the stream. In the bakery's file, grep Cinnamon finds nothing because every order spells it cinnamon in lowercase. Adding -i or fixing the pattern fixes the whole line.

Also watch for a stage waiting for input that never comes. On a real machine, a grep pattern with no file at the start of a pipeline waits for you to type. The practice terminal tells you instead; on a real one press Ctrl+C.

How is a real terminal different?

In a real shell the commands in a pipeline start at the same time and pass text along while they run, so head can stop a huge search early once it has its lines. The practice terminal runs the stages one after another, which gives the same output for files this size.

On a Mac, zsh pipes work the same way. Some options differ between GNU and BSD versions of head and tail, but -n 2 and -n +2 work on both.

What do AI tools get wrong with pipes?

Order of stages is the big one: a suggested head or tail placed before the filter, giving an answer that looks plausible and is wrong. Run the first stage alone and check that it holds every line you care about.

They also forget the header row in CSV files, change the case of words in your data, or repeat the file name after a pipe so a later stage reads the whole file again. Build the suggested pipeline yourself one stage at a time and compare each step's output with what the AI said it would be.

Field notes

  • A pipe sends what one command prints into the next command.
  • Build a pipeline one stage at a time and check the output after each step.
  • head and tail in a pipe act on the filtered lines, not the whole file.
  • Changing the order of the stages can change the answer.

Easy to get wrong

head cuts the file before grep looks, so you only search the first three lines. Filter first, then cut.

Naming the file again makes the second grep ignore the pipe and read the whole file, so the sourdough filter is lost.

The last line of the file is a collected order. Filter for the lines you want before taking the last one.

Questions people ask

Is there a limit to how many commands I can pipe?

No practical one. Pipelines of four or five stages are common. If one gets hard to read, put it in a script with a comment, which a later lesson covers.

Does a pipe save anything to disk?

No. The text passes from one command to the next in memory. To keep the result, add > file.txt at the end, which the lesson on saving output covers.

Why does my pipeline print nothing?

Some stage found nothing to pass on. Remove stages from the end, one at a time, until output appears. The last stage you removed is the one that emptied the stream.

What is the difference between | and ||?

A single | connects output to input. A double || means "run the next command only if this one failed". They look alike and do completely different jobs.

Keep going