Pipes & Redirection

Linux · Lesson 11 of 32 7 min read Updated

The real strength of the Linux command line is not any single command. It is the way small commands can be joined: the output of one becomes the input of the next, or goes into a file, or gets thrown away. This is done with redirection and pipes, and you will use them in almost every lesson from now on.

In this lesson you will learn the three standard streams, how to redirect each one, how to build pipelines, and a few advanced tricks such as here-documents and writing to root-owned files with tee.

Three standard streams

Every program starts with three connections already open, called streams. Each has a number, the file descriptor:

StreamNumberDefaultUsed for
Standard input (stdin)0The keyboardData the program reads
Standard output (stdout)1The terminalNormal results
Standard error (stderr)2The terminalError and warning messages

Because output and errors are separate streams, you can send them to different places, even though both normally appear on your screen mixed together:

Terminal
ls notes missing.txt
Output
ls: cannot access 'missing.txt': No such file or directory
notes:
shopping.txt  todo.txt

The first line came through stderr; the rest came through stdout.

Redirecting output to files

SyntaxMeaning
cmd > fileWrite stdout to file, replacing what was there
cmd >> fileAppend stdout to the end of file
cmd 2> fileWrite stderr to file
cmd > file 2>&1Send both stdout and stderr to file
cmd &> fileBash shortcut for the same thing
cmd > /dev/nullThrow stdout away
Terminal
ls notes missing.txt > out.txt
ls notes missing.txt > out.txt 2> err.txt
cat err.txt
echo "Buy milk" >> notes/todo.txt
tail -n 2 notes/todo.txt
Output
ls: cannot access 'missing.txt': No such file or directory
ls: cannot access 'missing.txt': No such file or directory
Call Kabir
Buy milk

In the first command only stdout went into out.txt, so the error still appeared on screen. In the second, the error went into err.txt and nothing appeared at all. Then >> added a line to the to-do list without destroying it.

2>&1 means "send stream 2 to wherever stream 1 is going right now". The order matters: cmd > file 2>&1 puts both in the file, but cmd 2>&1 > file sends errors to the screen, because when 2>&1 ran, stream 1 still pointed at the terminal.

/dev/null is a special file that discards everything written to it. command 2> /dev/null hides error messages, which is common in scripts, for example find / -name "*.conf" 2> /dev/null to skip the many "Permission denied" lines.

Redirecting input

< feeds a file into a command's stdin:

Terminal
wc -l < orders.csv
wc -l orders.csv
Output
9
9 orders.csv

With <, wc reads an anonymous stream and prints only the number, which is handy in scripts. When you name the file as an argument, wc prints the name too.

Pipes: joining commands

A pipe | connects the stdout of one command to the stdin of the next. Each command does one small job, and together they answer real questions. Which city has the most orders?

Terminal
cut -d, -f3 orders.csv | tail -n +2 | sort | uniq -c | sort -rn
Output
3 Pune
2 Kochi
2 Jaipur
1 Lucknow

Read it left to right: cut takes column 3 (the city), tail -n +2 drops the header line, sort puts equal cities next to each other, uniq -c counts each group, and sort -rn sorts by that count, largest first. You will learn each of these tools in the next few lessons; for now, notice how the data flows through the pipe one step at a time.

Pipes only carry stdout. Error messages still go to the screen, which is usually what you want. To send errors through a pipe as well, use 2>&1 | or the Bash shortcut |&.

tee: save and show at the same time

tee reads stdin and writes it both to the screen and to a file, like a T-shaped pipe joint. It is useful when you want to see output and keep a copy:

Terminal
sudo apt update 2>&1 | tee ~/apt-update.log
echo "192.168.1.40 printer.home" | sudo tee -a /etc/hosts

The second line solves a classic problem. sudo echo "..." >> /etc/hosts fails with "Permission denied", because the redirection is done by your shell, which is not root; only echo ran with sudo. Piping into sudo tee -a makes a root process do the writing (-a appends instead of replacing).

Here-documents and here-strings

A here-document feeds several lines of text into a command, up to a marker word you choose (often EOF). Variables and $(...) inside it are expanded:

Terminal
cat <<EOF > notes/server-info.txt
Server: $(hostname)
User: $USER
Date: $(date +%F)
EOF
cat notes/server-info.txt
tr a-z A-Z <<< "jaipur"
Output
Server: laptop
User: priya
Date: 2026-10-30
JAIPUR

Quote the marker (<<'EOF') to stop expansion when you want the text exactly as written, such as when creating a script. <<< is a here-string: it feeds one short string as stdin.

Advanced: process substitution

Some commands only accept file names, not pipes. Process substitution, written <(command), runs a command and hands its output to another command as if it were a file. This compares the customer column of two order files without creating temporary files:

Terminal
diff <(cut -d, -f2 orders.csv) <(cut -d, -f2 orders-new.csv)
Output
4c4
< Kabir
---
> Meera

diff reports that line 4 holds Kabir in the first list and Meera in the second.

Common mistakes

  • Overwriting with > when you meant >>. Your log or list is replaced instead of extended.
  • Reading and writing the same file in one command. The file is emptied first. Use a temporary file.
  • Putting 2>&1 before > file. Errors still reach the screen. Write > file 2>&1.
  • sudo with redirection. sudo cmd > /etc/file fails because your shell opens the file. Use | sudo tee /etc/file.

Key points

  • Programs have stdin (0), stdout (1) and stderr (2).
  • > replaces a file, >> appends, 2> redirects errors, and &> or > file 2>&1 captures both.
  • < feeds a file to stdin; <<EOF here-documents feed several lines.
  • | sends one command's stdout into the next command's stdin.
  • tee saves and displays output together, and sudo tee writes to root-owned files.

Exercise

Write one command each to: save the list of files in /etc to etc-list.txt; add the date to the end of that file; run ls /etc /nothing and save only the error message to errors.txt while the normal output still shows on screen; count how many entries are in /etc using a pipe.

Show solution
Terminal
ls /etc > etc-list.txt
date >> etc-list.txt
ls /etc /nothing 2> errors.txt
ls /etc | wc -l
cat errors.txt
Output
...
208
ls: cannot access '/nothing': No such file or directory

> creates the list, >> appends without destroying it, and 2> captures only stderr. When ls writes into a pipe it prints one name per line, so wc -l counts the entries (your number will differ).

Quick Quiz

1. What does >> do?

2. Which command saves both normal output and errors into all.log?

3. What happens with sort names.txt > names.txt?

4. Why does echo "x" | sudo tee -a /etc/hosts work when sudo echo "x" >> /etc/hosts fails?

Learn Linux with a mentor

Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call