The design assumption is that a program should do one thing and communicate through streams of text, so that programs which know nothing about each other can be combined.
That works because of a small and rigid convention. Every process starts with three open channels, and almost every tool uses them in the same way.
The three channels
Channel zero is input, channel one is normal output, and channel two is error output. The separation of the last two is the important one, because it allows errors to reach a person while normal output flows onward into another program.
A tool that writes errors to the output channel breaks composition, since its complaints end up mixed into the data being processed. That is why the convention is followed strictly rather than loosely.
The operators
| Operator | Effect |
|---|---|
| > | Send output to a file, replacing it |
| >> | Send output to a file, appending |
| < | Take input from a file |
| 2> | Send errors to a file |
| 2>&1 | Send errors wherever output is currently going |
| | | Connect output of one program to input of the next |
The fifth row is the one that causes confusion, because it copies the current destination rather than establishing a permanent link. Placed before a redirect of the output channel it sends errors to the old destination, which is usually the terminal, and the result looks like it did nothing.
A pipe is not a file
A pipe is a buffer in kernel memory with a producer at one end and a consumer at the other. Nothing is written to disk, and the two programs run at the same time rather than in sequence.
When the buffer fills, the writing program is suspended until the reader consumes some of it. That back pressure is what allows a pipeline to process a file far larger than memory without any coordination between the stages.
What happens when the reader stops
If the consuming program exits early, the producer receives a signal indicating the pipe has no reader, and the default action is to terminate it.
This is why searching a very large file and stopping at the first match returns immediately rather than reading the whole file. It is also why a program that traps or ignores that signal can continue writing into nothing, which is a common cause of a pipeline that appears to hang after its output has already been displayed.
Exit status through a pipeline
A pipeline reports the exit status of its final stage only, so a failure in the middle is invisible if the last program succeeds.
This produces scripts that report success while having produced nothing, because an early stage failed and the later stages processed an empty stream perfectly. Shells provide both an array of individual statuses and an option to make the pipeline fail if any stage fails, and the option is worth setting by default in anything non trivial.
Where the model does not fit
Text streams assume records that can be processed as they arrive. Operations requiring the whole input before producing any output, such as sorting, buffer everything and remove the memory advantage.
Binary data passes through unchanged but cannot be manipulated by the text tools, interactive programs expecting a terminal misbehave when their input is a pipe, and structured formats where a record spans multiple lines defeat line oriented processing. Recognising these cases early is faster than discovering them halfway through a long command.
Note: the order of redirections is evaluated left to right and matters. Sending errors to the output channel before redirecting output leaves the errors pointing at the terminal, which is the single most common redirection mistake.