| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | bash 5.2.26-6.el10coreutils 9.5-8.el10_2 |
| Applies to | Ubuntu, Debian, Kali Linux, Linux Mint, Pop!_OS, Raspberry Pi OS, elementary OS, Zorin OS, Parrot OS, MX Linux, RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora, Arch Linux |
| Privilege | Normal user for most examples; sudo when writing protected paths with tee |
| Scope | File descriptors 0, 1, and 2; output and input redirection; stderr merging and ordering; pipes and multi-stage pipelines; tee, /dev/null, here documents and here strings; pipeline exit status with $?, PIPESTATUS, and pipefail. Does not cover process substitution, named pipes in depth, or full shell scripting. |
| Related guides | Linux command line Edit text files |
Redirection symbols look cryptic until you see them as file descriptors the shell rewires before a command runs. Once stdout, stderr, and stdin are separate channels in your head, 2>&1, pipes, and tee stop feeling like magic punctuation.
What Are stdin, stdout, and stderr?
Every command the shell starts inherits three standard streams. The kernel numbers them as file descriptors so the shell can redirect them independently.
| Stream | File descriptor | Typical use |
|---|---|---|
| stdin | 0 | Keyboard input, pipe, or file fed into the command |
| stdout | 1 | Normal program output |
| stderr | 2 | Errors and diagnostics |
ls prints matching paths on stdout and complaints on stderr. Run it against one real file and one missing path so you can see both streams on the terminal.
ls /etc/hosts missing-file-xyzSample output:
ls: cannot access 'missing-file-xyz': No such file or directory
/etc/hostsThe error line came from stderr (descriptor 2). The path line came from stdout (descriptor 1). Order on the terminal can vary, but the split is what matters when you redirect one stream and leave the other visible.
Linux Redirection Quick Reference
Keep this table nearby while you practice. The sections below walk through each row with real output.
| Syntax | Meaning |
|---|---|
> file |
Replace file with stdout |
>> file |
Append stdout |
< file |
Read stdin from file |
2> file |
Redirect stderr |
2>> file |
Append stderr |
> file 2>&1 |
Send stdout and stderr to the same file |
2>&1 > file |
Different result because of evaluation order |
command1 | command2 |
Pipe stdout of left command to stdin of right |
command |& other |
Pipe stdout and stderr (Bash) |
command | tee file |
Display and save stdout |
> /dev/null |
Discard stdout |
Redirect Standard Output
The > operator connects file descriptor 1 to a file. The shell creates or truncates that file before the command runs, so a careless redirect can wipe existing content.
Work in /tmp so examples stay easy to clean up. Create a fresh file with a single line using echo and redirection.
cd /tmp && echo 'line one' > out.txtRead the file to confirm stdout landed on disk.
cat /tmp/out.txtSample output:
line oneTo see why > is destructive, write one string, redirect again with a different string, and inspect the file.
echo 'original' > /tmp/trunc.txtecho 'replaced' > /tmp/trunc.txtcat /tmp/trunc.txtSample output:
replacedOnly the second string remains. Treat > like "start this file over" unless you intentionally want to discard what was there.
Append Output to a File
Use >> when you want to add lines without truncating the file. The first redirect creates the file; the second appends.
echo 'first' > /tmp/append.txtecho 'second' >> /tmp/append.txtcat /tmp/append.txtSample output:
first
secondRepeated logging and cron jobs usually use >> so each run adds a line instead of erasing the log.
Redirect Standard Error
Descriptor 2> sends stderr to a file while stdout can still print on the terminal. Ask ls for a path that does not exist and capture only the error.
ls missing-err-xyz 2> /tmp/err.logstderr no longer appears on the terminal. Inspect the error file.
cat /tmp/err.logSample output:
ls: cannot access 'missing-err-xyz': No such file or directoryWhen you redirect stdout alone, stderr still appears on the terminal. That surprises people who expect silence after >.
ls /etc/hosts missing-only-stdout-xyz > /tmp/out-only.txtSample output:
ls: cannot access 'missing-only-stdout-xyz': No such file or directoryThe file holds only the successful stdout line.
cat /tmp/out-only.txtSample output:
/etc/hostsRedirect stdout and stderr Separately
Split streams when you want a clean success log and a separate error log. Both redirections apply to the same command; order between > and 2> does not change the outcome here because each targets its own descriptor.
ls /etc/hosts missing-sep-xyz > /tmp/out.log 2> /tmp/err.logCheck stdout first.
cat /tmp/out.logSample output:
/etc/hostsThen stderr.
cat /tmp/err.logSample output:
ls: cannot access 'missing-sep-xyz': No such file or directoryAutomation that emails operators only on failure often keeps this split: stdout in a result file, stderr in an alert file.
Redirect stdout and stderr to the Same File
To merge both streams into one file, point stderr at wherever stdout already goes using 2>&1. The 2>&1 token means "make descriptor 2 a copy of descriptor 1," not "send stderr to a file named &1."
ls /etc/hosts missing-same-xyz > /tmp/all.log 2>&1cat /tmp/all.logSample output:
ls: cannot access 'missing-same-xyz': No such file or directory
/etc/hostsBash also offers &> as shorthand for the same merge when you do not need separate steps.
ls /etc/hosts missing-amp-xyz &> /tmp/amp.logcat /tmp/amp.logSample output:
ls: cannot access 'missing-amp-xyz': No such file or directory
/etc/hostsBoth forms capture errors and normal output in one place for later review.
Why Redirection Order Matters
The shell applies redirections left to right. That detail explains why > file 2>&1 and 2>&1 > file behave differently even though they look similar.
Correct order: open the file for stdout first, then duplicate stderr onto that same destination.
ls /etc/hosts missing-wrong-xyz > /tmp/order-good.log 2>&1cat /tmp/order-good.logSample output:
ls: cannot access 'missing-wrong-xyz': No such file or directory
/etc/hostsWrong order: stderr is duplicated to the terminal's stdout first, then stdout alone moves to the file.
ls /etc/hosts missing-wrong-xyz 2>&1 > /tmp/order-bad.logSample output:
ls: cannot access 'missing-wrong-xyz': No such file or directoryOnly stdout went to the file; stderr still hit the terminal.
cat /tmp/order-bad.logSample output:
/etc/hostsWalk through the wrong case in order: 2>&1 copies stderr onto the current stdout (your terminal). Then > order-bad.log moves stdout to the file, leaving stderr on the terminal. Always place 2>&1 after the redirect that defines where stdout should go.
Redirect Standard Input
The < operator feeds a file to stdin (descriptor 0). Word count is a clear example because it reads from stdin when you do not pass a filename.
wc -l < /etc/hostsSample output:
9Many commands accept a filename argument directly, so < is optional for them.
wc -l /etc/hostsSample output:
9 /etc/hostsUse < when the command only reads stdin, when you build pipelines, or when you want the same script to read from a file or a pipe without changing the command name.
Use Pipes to Connect Commands
A pipe | connects stdout of the command on the left to stdin of the command on the right. stderr is not included unless you redirect it separately or use Bash |&.
Sort lines fed from printf to show the data flow.
printf 'b\na\nc\n' | sortSample output:
a
b
cprintf wrote to stdout; sort read those lines from stdin and wrote sorted lines to stdout.
Filter running processes with grep the same way.
ps aux | grep '[s]shd' | head -2Sample output:
root 1193 0.0 0.1 9160 6684 ? Ss 22:42 0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root 2218 0.0 0.1 15680 10708 ? Ss 22:43 0:00 sshd-session: root [priv]Search recent journal lines through a pipe when you want live filtering without saving the full log first.
journalctl -u sshd --no-pager -n 3 | grep -i sshd | head -2Sample output:
Aug 07 22:47:49 vm1.lab.example sshd-session[3832]: pam_unix(sshd:session): session opened for user root(uid=0) by root(uid=0)
Aug 07 22:47:50 vm1.lab.example sshd-session[3836]: Accepted password for root from 10.0.2.2 port 65440 ssh2If a pipeline must forward stderr as well, Bash |& pipes both streams. This example counts every line ls prints, including its error message.
ls /etc/hosts missing-pipe-xyz |& wc -lSample output:
2Build Multi-Stage Pipelines
Long pipelines are easier to read when you know what each stage consumes and produces. Start with two words on stdin, keep only lines that match ok, then count how many survived.
printf 'error line\nok line\n' | grep ok | wc -lSample output:
1After printf, stdout held two lines. grep ok passed one line forward. wc -l read that single line and printed the count. Add a third stage only when each step still has a clear input and output; unreadable one-liners hide failures in the middle.
Save and Display Output with tee
tee copies stdin to stdout and to a file, so you watch output live while you save a copy. See the tee command for -a and other flags; here we focus on redirection patterns.
printf 'visible\n' | tee /tmp/tee-demo.txtSample output:
visibleThe same text is on disk.
cat /tmp/tee-demo.txtSample output:
visibleAppend with -a when the file should keep prior content.
printf 'more\n' | tee -a /tmp/tee-demo.txtcat /tmp/tee-demo.txtSample output:
visible
moreShell redirection runs as your user before an elevated command starts. sudo echo text > /etc/protected.conf fails for a normal user because the shell opens /etc/protected.conf without root privileges. Pipe to tee so the program that opens the file runs under sudo.
printf 'admin note\n' | sudo tee /etc/redir-demo.confcat /etc/redir-demo.confSample output:
admin notetee opened the path with root privileges while still printing the line to your terminal.
Discard Output with /dev/null
/dev/null is a special device that accepts writes and discards them. It is the standard way to silence a stream without leaving an empty file to clean up.
Silence stderr when you only care whether a command succeeded.
ls missing-null-xyz 2>/dev/nullThe command exits with no stderr line on the terminal.
Discard stdout while keeping stderr visible.
ls /etc/hosts > /dev/nullNothing is printed because stdout was redirected to /dev/null.
Send both streams away when neither is useful.
ls /etc/hosts missing-both-xyz > /dev/null 2>&1The command runs quietly; check $? if you need the exit status.
Redirect Input with Here Documents and Here Strings
Here documents and here strings feed stdin without a separate file on disk. They are common in scripts; this section covers the syntax only.
A here document with a quoted delimiter disables expansion inside the block.
cat << 'EOF' > /tmp/heredoc.txt
line a
line b
EOFcat /tmp/heredoc.txtSample output:
line a
line bA here string passes one string on stdin. <<< is a Bash extension.
grep -F hosts <<< 'search hosts in this string'Sample output:
search hosts in this stringFor loops, functions, and larger scripts, continue with Bash shell scripting rather than growing this lesson into a scripting manual.
Pipeline Exit Status
$? holds the exit status of the last command. In a pipeline, Bash normally reports the status of the rightmost command unless you change shell options.
Run a failing command piped to a succeeding one and inspect $?.
false | trueecho $?Sample output:
0The pipeline looks successful even though false failed. Check every stage with PIPESTATUS.
false | trueecho PIPESTATUS:${PIPESTATUS[@]}Sample output:
PIPESTATUS:1 0Enable pipefail so a failure in an earlier pipeline stage can make the whole pipeline non-zero. The returned status is from the rightmost command that exited non-zero. Bash still waits for every stage in the pipeline; pipefail does not make the shell fail fast mid-pipe.
set -o pipefailfalse | trueecho $?Sample output:
1Use pipefail in scripts that grep or filter logs so a broken upstream command does not masquerade as success.
Common Redirection Mistakes
| Symptom | Likely cause | Fix |
|---|---|---|
| File emptied after redirect | > truncates before the command runs |
Use >> to append, or copy the file first |
| Errors still on screen | Only stdout was redirected | Add 2> file, 2>&1, or &> |
Permission denied with sudo echo > /etc/file |
Shell opens the file as your user | Pipe to sudo tee /path/file |
| stderr missing from combined log | Used 2>&1 > file instead of > file 2>&1 |
Put the stdout redirect first, then 2>&1 |
| Pipeline "succeeds" but data wrong | Earlier stage failed silently | Check PIPESTATUS or set -o pipefail |
| Unexpected words in output | Unquoted redirection target expanded | Quote paths and use << 'EOF' when literals matter |
Practical Examples
These patterns mirror day-to-day administration: capture output, split errors, search logs, save filtered results, and drop noise.
Capture check-update text in one file and errors in another.
dnf check-update --quiet 2> /tmp/dnf-err.log > /tmp/dnf-out.logdnf check-update returns 100 when updates are available, 0 when none are available, and 1 on error, so status 100 is not a command failure.
wc -l /tmp/dnf-out.log /tmp/dnf-err.logSample output:
60 /tmp/dnf-out.log
0 /tmp/dnf-err.log
60 totalSearch a saved log through grep without re-running the heavy command.
grep -i error /tmp/dnf-out.log | head -3Save filtered journal lines while watching them live.
journalctl -u sshd --no-pager -n 5 | tee /tmp/sshd-recent.log | grep -i acceptedHide benign "file not found" noise from a script loop.
find /etc -name 'redir-demo.conf' 2>/dev/nullSample output:
/etc/redir-demo.confReferences
- Bash redirections — GNU Bash manual
- Bash pipelines — GNU Bash manual
- tee(1) — Linux man page
- null(4) —
/dev/nulldevice
Summary
You started with three standard streams—stdin, stdout, and stderr—and saw how file descriptors 0, 1, and 2 let the shell redirect them independently. Redirecting stdout with > or >>, stderr with 2>, and both with > file 2>&1 or &> gives you precise control over what lands on the terminal, in a log file, or in the bit bucket.
The ordering lesson is the one most tutorials skip: 2>&1 duplicates stderr onto whatever stdout currently uses, so > file must come before 2>&1 when you want both streams in the same file. Pipes wire stdout to stdin on the next command; stderr stays behind unless you pipe it with |& or merge it first. tee solves the sudo problem by letting the elevated program open the protected path while you still see output live.
Pipeline exit status is the other hidden footgun: without pipefail, a failed grep or curl early in the chain can leave $? at zero because only the last command counts. Check PIPESTATUS while debugging, then enable pipefail in scripts that need to detect failures anywhere in a pipeline.

