Pipes, filters and redirection
Three streams, a handful of operators, and two traps that catch everybody: the order of 2>&1 relative to >, and a pipeline that reports success because only the last command's status survives. This guide shows both failing, then both fixed, plus tee for writing to a file you cannot open and xargs -0 for filenames you did not choose.
Essential Commands Guide 7 of 38 Intermediate
- OSUbuntu 26.04 LTS
- LVM2.03.31 (Ubuntu) / 2.03.36 (AlmaLinux)
- nftables1.1.6 (Ubuntu) / 1.1.5 (AlmaLinux)
- TimeAbout 18 min
&> and |&, set -o pipefail and PIPESTATUS are bash features - the login shell here is zsh, so the examples run under an explicit bash -c. POSIX sh has none of them.
- Firewallufw 0.36.2 enabled but reporting inactive / firewalld active
- Network confignetplan + systemd-networkd / NetworkManager 1.56.0
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| LFCS-A01 | 192.168.0.70 | Ubuntu 26.04 LTS | Primary host - most guides run only here | 2 Core | 4 GB | 50 GB |
This guide includes
Use this for the two traps that catch everybody. This matters because the order of 2>&1 and > is not a style choice - swapped, the errors end up somewhere you did not intend.
- following the three streams, and where each one goes
- finding that the order of the redirections decides the result
- appending, truncating and discarding, and using
teefor both at once - seeing a pipeline hide every exit status but the last
- using
xargson a filename with a space, and feeding a command from a heredoc
Before you start
- guide 6 - the filters going in the pipe.
-
Three streams, and where each one goes
One command that writes to both, redirected four ways.
bash Example session mkdir -p ~/ess/p && cd ~/ess/p && bash -c 'echo "to stdout"; echo "to stderr" >&2' ; echo "--- both reached the terminal above"to stdoutto stderr--- both reached the terminal abovecd ~/ess/p && bash -c 'echo "to stdout"; echo "to stderr" >&2' > out.txt; echo "stdout captured, stderr still on screen"; echo "--- out.txt holds:"; cat out.txtto stderrstdout captured, stderr still on screen--- out.txt holds:to stdoutcd ~/ess/p && bash -c 'echo "to stdout"; echo "to stderr" >&2' 2> err.txt; echo "--- err.txt holds:"; cat err.txtto stdout--- err.txt holds:to stderrcd ~/ess/p && bash -c 'echo "to stdout"; echo "to stderr" >&2' > both.txt 2>&1; echo "--- both.txt holds:"; cat both.txt--- both.txt holds:to stdoutto stderrExpected result
> out.txtcapturing only stdout and leaving stderr on screen;2> err.txtthe reverse;> both.txt 2>&1capturing both.Success conditionYou can send each stream where you want it.
-
And the order of the redirections is not a style choice
The same two operators, swapped.
bash Example session cd ~/ess/p && bash -c 'ls /nonexistent 2>&1 > wrong.txt'; echo "--- wrong.txt holds:"; cat wrong.txt; echo "(empty - stderr went to the OLD stdout, the terminal)"ls: cannot access '/nonexistent': No such file or directory--- wrong.txt holds:(empty - stderr went to the OLD stdout, the terminal)cd ~/ess/p && bash -c 'ls /nonexistent > right.txt 2>&1'; echo "--- right.txt holds:"; cat right.txt--- right.txt holds:ls: cannot access '/nonexistent': No such file or directoryExpected result
2>&1 > wrong.txtleaving wrong.txt empty with the error on screen;> right.txt 2>&1capturing the error.Success conditionYou will not write the broken form again.
-
Appending, truncating, and discarding
>against>>, and/dev/null.bash Example session cd ~/ess/p && printf 'first\n' > trunc.txt && printf 'second\n' > trunc.txt && cat trunc.txt; echo "--- > truncated it"second--- > truncated itcd ~/ess/p && printf 'first\n' > app.txt && printf 'second\n' >> app.txt && cat app.txt; echo "--- >> appended"firstsecond--- >> appendedcd ~/ess/p && ls /nonexistent 2>/dev/null; echo "exit $? and no message"; ls /nonexistent >/dev/null 2>&1; echo "exit $? and nothing at all"exit 2 and no messageexit 2 and nothing at allExpected result
>leaving onlysecond;>>leaving both lines; and2>/dev/nullhiding the message whileexit 2survives.Success conditionYou can choose between replacing and adding.
-
tee, for when you want both
Down the pipe and into a file at the same time - including a file you cannot open.
bash Example session cd ~/ess/p && printf 'a\nb\nc\n' | tee copy.txt | wc -l; echo "--- and copy.txt holds:"; cat copy.txt3--- and copy.txt holds:abccd ~/ess/p && printf 'd\n' | tee -a copy.txt >/dev/null && cat copy.txtabcdcd ~/ess/p && echo "written as root" | sudo tee root-owned.txt >/dev/null && ls -l root-owned.txt; echo "--- which is how you write to a file you cannot open yourself"-rw-r--r-- 1 root root 16 Aug 29 09:42 root-owned.txt--- which is how you write to a file you cannot open yourselfExpected result
3fromwc -lwithcopy.txtalso written;-aappending a fourth line; and a root-owned file created bysudo tee.Success conditionYou can capture and pass through in one step.
-
A pipeline hides every exit status but the last
false | true, three ways of asking what happened.bash Example session cd ~/ess/p && bash -c 'false | true; echo "plain pipeline exit: $?"'plain pipeline exit: 0cd ~/ess/p && bash -c 'set -o pipefail; false | true; echo "with pipefail: $?"'with pipefail: 1cd ~/ess/p && bash -c 'false | true; echo "PIPESTATUS: ${PIPESTATUS[*]}"'PIPESTATUS: 1 0Expected result
plain pipeline exit: 0,with pipefail: 1, andPIPESTATUS: 1 0.Success conditionYou can detect a failure in the middle of a pipeline.
-
xargs, and the filename with a space in it
Turning a list of names into arguments - and the version that survives real filenames.
bash Example session cd ~/ess/p && touch f1.log f2.log f3.log && ls *.log | xargs -n1 echo "would process"would process f1.logwould process f2.logwould process f3.logcd ~/ess/p && printf 'one file.log\n' > 'one file.log' && ls *.log | xargs ls -l 2>&1 | tail -3; echo "--- names with spaces break a plain xargs"-rw-rw-r-- 1 sysadmin sysadmin 0 Aug 29 09:42 f1.log-rw-rw-r-- 1 sysadmin sysadmin 0 Aug 29 09:42 f2.log-rw-rw-r-- 1 sysadmin sysadmin 0 Aug 29 09:42 f3.log--- names with spaces break a plain xargscd ~/ess/p && find . -maxdepth 1 -name '*.log' -print0 | xargs -0 ls -l | wc -l; echo "--- -print0 and -0 make it safe"4--- -print0 and -0 make it safecd ~/ess/p && find . -maxdepth 1 -name '*.log' -exec ls -l {} + | wc -l; echo "--- or skip xargs entirely with -exec ... +"4--- or skip xargs entirely with -exec ... +Expected result
xargs -n1echoing one per line; a plainls | xargsfailing onone file.log; and both-print0 | xargs -0and-exec ... +handling it.Success conditionYou can act on a list of files safely.
-
A heredoc and a here-string
Feeding a command several lines, or one, without a file.
bash Example session cd ~/ess/h && bash unquoted.shan unquoted heredoc expands /home/sysadmin and lfcs-a01cd ~/ess/h && bash quoted.sha quoted heredoc leaves $HOME and $(hostname) alonecd ~/ess/h && printf 'cat <<-EOF\n\tthis line was indented with a TAB\nEOF\n' > dash.sh && cat -A dash.sh | head -3; echo "--- and run:"; bash dash.shcat <<-EOF$^Ithis line was indented with a TAB$EOF$--- and run:this line was indented with a TABcd ~/ess/h && bash -c 'tr a-z A-Z <<< "a here-string is one line"'; bash -c 'wc -w <<< "three words here"'A HERE-STRING IS ONE LINE3Expected resultThe unquoted heredoc expanding to
/home/sysadmin and lfcs-a01; the quoted one printing$HOME and $(hostname)literally;cat -Ashowing the^Itab that<<-strips.Success conditionYou can supply input inline.
Troubleshooting
A redirected log file is empty even though the command failed.
Why:
2>&1 > file- stderr was pointed at the terminal before stdout moved.Fix:
> file 2>&1, or&> file.sort file > fileproduced an empty file.Why: The shell truncates the target before the command runs.
Fix:
sort file > tmp && mv tmp file, orsort -o file file.sudo echo text > /etc/filesays permission denied.Why: The shell opens the file as you, not as root.
Fix:
echo text | sudo tee /etc/file.A pipeline reports success although an early stage failed.
Why: Only the last command's status is the pipeline's status.
Fix:
set -o pipefail, or inspect${PIPESTATUS[@]}.xargsfails on filenames with spaces.Why: It splits on whitespace by default.
Fix:
find ... -print0 | xargs -0, orfind ... -exec cmd {} +.A heredoc wrote literal
$VARinto a file.Why: The delimiter was quoted -
<<'EOF'.Fix:Unquote it to expand. Quote it deliberately when you want the text verbatim.
set -o pipefailis not recognised.Why: The script is running under
sh, not bash.Fix:
#!/usr/bin/env bash.