Pipes & Redirection
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:
| Stream | Number | Default | Used for |
|---|---|---|---|
| Standard input (stdin) | 0 | The keyboard | Data the program reads |
| Standard output (stdout) | 1 | The terminal | Normal results |
| Standard error (stderr) | 2 | The terminal | Error 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:
ls notes missing.txtls: 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
| Syntax | Meaning |
|---|---|
cmd > file | Write stdout to file, replacing what was there |
cmd >> file | Append stdout to the end of file |
cmd 2> file | Write stderr to file |
cmd > file 2>&1 | Send both stdout and stderr to file |
cmd &> file | Bash shortcut for the same thing |
cmd > /dev/null | Throw stdout away |
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.txtls: 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:
wc -l < orders.csv
wc -l orders.csv9 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?
cut -d, -f3 orders.csv | tail -n +2 | sort | uniq -c | sort -rn3 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:
sudo apt update 2>&1 | tee ~/apt-update.log
echo "192.168.1.40 printer.home" | sudo tee -a /etc/hostsThe 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:
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"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:
diff <(cut -d, -f2 orders.csv) <(cut -d, -f2 orders-new.csv)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>&1before> file. Errors still reach the screen. Write> file 2>&1. sudowith redirection.sudo cmd > /etc/filefails 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>&1captures both.<feeds a file to stdin;<<EOFhere-documents feed several lines.|sends one command's stdout into the next command's stdin.teesaves and displays output together, andsudo teewrites 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
ls /etc > etc-list.txt
date >> etc-list.txt
ls /etc /nothing 2> errors.txt
ls /etc | wc -l
cat errors.txt... 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?
>> appends; a single > replaces the file's contents.
2. Which command saves both normal output and errors into all.log?
Redirect stdout to the file first, then point stderr at the same place.
3. What happens with sort names.txt > names.txt?
The shell truncates the output file before sort reads it.
4. Why does echo "x" | sudo tee -a /etc/hosts work when sudo echo "x" >> /etc/hosts fails?
The redirection >> is done by your own shell; sudo tee makes the writer itself root.
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
