Ravindra BagaleCourses & study guides मराठी Track your progress

Shell Scripting Complete 10: Redirection, Pipes, tee, Here-docs and Process Substitution

Every Linux command has three doors: input (stdin), normal output (stdout) and error output (stderr). Redirection decides where each door points. In this chapter you learn > and >> to write files, 2> and &> for errors, < to read, pipes | to join commands, tee to save and show, here-docs <

What you'll learn in this chapter

  • Know stdin, stdout and stderr (0, 1, 2)
  • Write and append with > and >>
  • Split or join errors with 2> and 2>&1
  • Build pipes and use tee
  • Write files with here-docs
  • Compare outputs with <( )

1. An everyday example: water pipes in a building

Why do we need this?

In a Kothrud society, water comes from the tank (input), goes to the kitchen tap (normal output), and waste water goes to a separate drain (errors). A plumber can send the tap water into a bucket instead, join two pipes, or add a T-joint so water goes two ways at once. Redirection and pipes are plumbing for data.

What is it?

Tank = stdin (0). Tap = stdout (1). Drain = stderr (2). Bucket = a file with >. Joining pipes = |. T-joint = tee. A tanker delivering many litres at once = a here-doc.

How to do it (step by step)

Where does each stream go?Command starts with 0,1,2 on the terminal> or >> moves 1 to a file2> moves 2 to a file2>&1 points 2 to where 1 points connects 1 to the next command's 0tee copies to file and screen
Order matters: > file 2>&1, not 2>&1 > file.

2. Write and append: > and >>

Why do we need this?

Reports, logs and generated configs must land in files, not just on the screen.

What is it?

cmd > file writes stdout to file and replaces old content. cmd >> file adds to the end. > file alone (or : > file) empties a file without deleting it, good for log files a program has open. set -o noclobber stops > from overwriting by mistake; >| forces it.

FormDoes
> foverwrite
>> fappend
: > fempty the file
< fread input from f
>| foverwrite even with noclobber

How to do it (step by step)

  1. Save a list.
ls /etc > etc-list.txt; wc -l etc-list.txt

Output:

212 etc-list.txt

What it means: Screen shows nothing.

  1. Append a line.
date >> run.log; date >> run.log; wc -l run.log

Output:

2 run.log

What it means: Two lines.

  1. Empty a log.
: > /var/log/app/app.log; ls -l /var/log/app/app.log

Output:

-rw-r--r-- 1 app app 0 Oct 25 10:12 /var/log/app/app.log

What it means: Size 0, file kept.

  1. Protect files.
set -o noclobber; echo hi > etc-list.txt

Output:

bash: etc-list.txt: cannot overwrite existing file

What it means: Safety on.

  1. Read from a file.
wc -l < etc-list.txt

Output:

212

What it means: No file name printed.

3. Errors: 2>, &> and 2>&1

Why do we need this?

In cron jobs you want errors in a log, and noisy warnings away from your report.

What is it?

2> file sends only errors to file. 2>/dev/null throws errors away. cmd > out.log 2>&1 sends both to the same file: first move 1, then point 2 at 1. &> file is the bash short form for both. |& pipes both to the next command. Do not hide errors you need; hide only known noise.

How to do it (step by step)

  1. Errors only.
find / -name '*.pem' 2> find-errors.txt | head -2

Output:

/etc/pki/tls/cert.pem
/workspace/key.pem

What it means: Permission errors went to the file.

  1. Hide noise.
find / -name sshd_config 2>/dev/null

Output:

/etc/ssh/sshd_config

What it means: Clean output.

  1. Both in a log.
./backup.sh > backup.log 2>&1; tail -2 backup.log

Output:

tar: /data/x: Cannot open: Permission denied
backup done

What it means: Errors and output together.

  1. Short form.
./backup.sh &>> backup.log

What it means: Append both.

  1. Separate files.
./import.sh > ok.log 2> err.log

What it means: Easy to check err.log size.

4. Pipes | and tee

Why do we need this?

Small tools joined together answer big questions: which IP hit us most, which product sold most.

What is it?

a | b sends a's stdout into b's stdin. Chain as many as you need. tee file writes to file and passes data on, so you can save a middle step and still see it. tee -a appends. sudo tee is the way to write a root-owned file, because sudo echo x > file does not give the > root rights.

How to do it (step by step)

  1. Top IPs.
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -3

Output:

  540 49.36.12.8
  311 103.21.4.9
   97 66.249.66.1

What it means: Five tools, one answer.

  1. Save and show.
df -h | tee disk-report.txt

Output:

Filesystem Size Used Avail Use% Mounted on
/dev/xvda1 20G 8.1G 12G 41% /

What it means: Seen and saved.

  1. Root file.
echo '10.0.1.5 db1' | sudo tee -a /etc/hosts

Output:

10.0.1.5 db1

What it means: Works where sudo echo > fails.

  1. Count lines in a pipe.
journalctl -u nginx --since today | grep -c error

Output:

4

What it means: Errors today.

5. Here-docs: <

Why do we need this?

Writing a config file, an SQL script or an email body line by line with echo is ugly and error-prone.

What is it?

cmd <

FormEffect
<expand $vars
<<'EOF'no expansion
<<-EOFstrip leading tabs
<<<one string as input

How to do it (step by step)

  1. Write a config.
app=shop; cat > /tmp/app.conf <<EOF
name=$app
port=8080
EOF
cat /tmp/app.conf

Output:

name=shop
port=8080

What it means: $app expanded.

  1. Keep $ as text.
cat > /tmp/hello.sh <<'EOF'
echo "Hello $USER"
EOF
cat /tmp/hello.sh

Output:

echo "Hello $USER"

What it means: Quoted word, no expansion.

  1. SQL in one go.
psql -h db1 -U app shop <<'SQL'
SELECT city, count(*) FROM orders GROUP BY city;
SQL

Output:

  city  | count
 Pune   |  1200

What it means: Many lines to psql.

  1. Root file with here-doc.
sudo tee /etc/motd > /dev/null <<'EOF'
Authorized users only. Managed by ops team.
EOF

What it means: tee writes as root.

6. Here-strings <<<

Why do we need this?

Sometimes you have one value in a variable and a command wants it on stdin.

What is it?

cmd <<< "$v" feeds the string plus a newline to cmd. It avoids echo "$v" | cmd and, unlike a pipe, read runs in the current shell, so variables it sets stay after.

How to do it (step by step)

  1. Split a CSV line.
IFS=, read -r id city amt <<< '101,Pune,499'; echo "$city $amt"

Output:

Pune 499

What it means: Variables stay set.

  1. Count words.
wc -w <<< 'build test deploy'

Output:

3

What it means: No echo needed.

  1. bc input.
bc <<< 'scale=2; 22/7'

Output:

3.14

What it means: Short maths.

7. Process substitution <( ) and >( )

Why do we need this?

diff and comm want two files, but you have two command outputs, for example packages on two servers.

What is it?

<(cmd) runs cmd and gives a file name like /dev/fd/63 that reads its output. You can pass it to any command that wants a file. >(cmd) is the reverse: what you write goes into cmd. while read ... done < <(cmd) loops over output without the subshell problem of a pipe, so counters survive.

How to do it (step by step)

  1. Diff two servers.
diff <(ssh web1 'rpm -qa | sort') <(ssh web2 'rpm -qa | sort')

Output:

< nginx-1.24.0-1
> nginx-1.22.1-1

What it means: Version drift found.

  1. What is it?
echo <(true)

Output:

/dev/fd/63

What it means: A file name.

  1. Loop keeps counter.
n=0; while read -r u; do ((n++)); done < <(cut -d: -f1 /etc/passwd); echo $n

Output:

34

What it means: With a pipe n would be 0.

  1. Write to two places.
./export.sh | tee >(gzip > export.gz) | wc -l

Output:

5000

What it means: Count and compress at once.

8. File descriptors and exec

Why do we need this?

Long scripts want all output logged without adding > log to every line.

What is it?

exec > file redirects the rest of the script's stdout. exec > >(tee -a run.log) 2>&1 logs everything while still showing it. exec 3> file opens a new descriptor 3; write with >&3 and close with exec 3>&-. /dev/null is the black hole; /dev/stderr is a name for fd 2.

How to do it (step by step)

  1. Log the whole script.
exec > >(tee -a /var/log/nightly.log) 2>&1
echo 'nightly start'

Output:

nightly start

What it means: Every later line is logged.

  1. Extra descriptor.
exec 3>> audit.log; echo 'user asha ran deploy' >&3; exec 3>&-; cat audit.log

Output:

user asha ran deploy

What it means: Audit kept apart.

  1. Read from fd.
exec 4< servers.txt; read -r first <&4; echo $first

Output:

web1

What it means: Read one line.

9. Real examples from many industries

Why do we need this?

The same Linux skill shows up in every kind of company. Seeing it in many places helps you remember it.

What is it?

Each row is a made-up team. It shows who uses this and how.

How to do it (step by step)

Team and cityHow they use itCommand
Rohan, banking, MumbaiSeparate error log for EOD batch./eod.sh > eod.log 2> eod.err
Emma, media, BerlinTop referrers from access logawk '{print $11}' a.log | sort | uniq -c | sort -rn
Kenji, robotics, TokyoWrite device config with here-doccat > dev.conf <<EOF
Fatima, government, RiyadhCompare user lists of two serverscomm -3 <(sort a) <(sort b)
Lucas, SaaS, Sao PauloLog whole deploy scriptexec > >(tee -a deploy.log) 2>&1
Pooja, pharma, HyderabadAppend to root-owned hostsecho "$ip db" | sudo tee -a /etc/hosts

10. Common errors and how to fix them

Why do we need this?

Everyone hits these errors. Knowing the cause saves hours.

What is it?

Read the exact message first. Then match it to a row below.

How to do it (step by step)

Error you seeWhy it happensHow to fix
Permission denied with sudo echo x > file> runs as you, not rootUse sudo tee.
echo x | sudo tee file
Errors still on screen after 2>&1 > fWrong orderPut > f first.
cmd > f 2>&1
File became empty> on a file you readWrite to a temp then move.
sort f > f.tmp && mv f.tmp f
warning: here-document delimited by end-of-fileClosing word missing or indentedPut EOF alone at line start.
EOF
Variables empty after while loopPipe runs loop in a subshellUse < <(cmd).
done < <(cmd)
cannot overwrite existing filenoclobber is onUse >| or set +o noclobber.
echo x >| f
syntax error near unexpected token `('Process substitution in shRun with bash.
bash script.sh
$VAR printed literally in fileUsed <<'EOF'Use unquoted EOF to expand.
cat <<EOF
Disk full from a log>> never rotatedEmpty or rotate it.
: > big.log

11. Troubleshooting step by step

Why do we need this?

When output goes to the wrong place, test each stream on its own.

What is it?

Go top to bottom. Stop at the first step that shows the problem.

How to do it (step by step)

  1. Only stdout.
cmd 2>/dev/null
  1. Only stderr.
cmd > /dev/null
  1. See open fds.
ls -l /proc/$$/fd
  1. Check order.
cmd > f 2>&1
  1. Pipe statuses.
echo "${PIPESTATUS[@]}"
  1. Lint.
shellcheck s.sh

12. Lab 1: Log report pipeline with tee

Why do we need this?

Build a small access log, find the top IPs and status codes with a pipe, and save the report while seeing it.

What is it?

A full hands-on lab. Run every step on a test Linux machine (an EC2 instance, a VM, or WSL).

How to do it (step by step)

  1. Make a log with a here-doc.
cat > ~/access.log <<'EOF'
49.36.12.8 - - [25/Oct/2026] "GET / HTTP/1.1" 200
49.36.12.8 - - [25/Oct/2026] "GET /cart HTTP/1.1" 500
103.21.4.9 - - [25/Oct/2026] "GET / HTTP/1.1" 200
49.36.12.8 - - [25/Oct/2026] "GET /pay HTTP/1.1" 200
EOF
  1. Top IPs.
awk '{print $1}' ~/access.log | sort | uniq -c | sort -rn

Output:

      3 49.36.12.8
      1 103.21.4.9
  1. Status codes saved.
awk '{print $NF}' ~/access.log | sort | uniq -c | tee ~/status.txt

Output:

      3 200
      1 500
  1. Errors to a file.
grep ' 500$' ~/access.log > ~/errors.txt; wc -l < ~/errors.txt

Output:

1
  1. Clean up.
rm ~/access.log ~/status.txt ~/errors.txt

13. Lab 2: Compare two configs with process substitution

Why do we need this?

Write two configs with here-docs, then compare them sorted and without comments using <( ) and no temp files.

What is it?

A full hands-on lab. Run every step on a test Linux machine (an EC2 instance, a VM, or WSL).

How to do it (step by step)

  1. Config A.
cat > ~/a.conf <<'EOF'
# prod
port=80
workers=4
EOF
  1. Config B.
cat > ~/b.conf <<'EOF'
workers=8
port=80
# staging
EOF
  1. Plain diff is noisy.
diff ~/a.conf ~/b.conf | wc -l

Output:

8
  1. Clean compare.
diff <(grep -v '^#' ~/a.conf | sort) <(grep -v '^#' ~/b.conf | sort)

Output:

2c2
< workers=4
---
> workers=8
  1. Clean up.
rm ~/a.conf ~/b.conf

Ravindra Bagale's Tip

Remember the three doors 0, 1 and 2. Write > file 2>&1 in that order, use tee when you need to save and see, use <<'EOF' when writing scripts or configs into files, and use < <(cmd) when a loop must keep its variables.

14. Frequently asked questions

Q. What is /dev/null?

A file that throws data away.

Q. > vs >>?

Replace vs add.

Q. &> in sh?

No, use > f 2>&1.

Q. Here-doc indent?

Use <<- with tabs.

Q. Why tee with sudo?

Redirect runs as you.

Q. Is <( ) a real file?

A pipe shown as /dev/fd/N.

Q. Pipe both streams?

Use |&.

Q. Edit a file in place with >?

No; temp file then mv.

15. Interview questions and answers

Beginner level

Q. What is fd 2?

stderr.

Q. Append to a file?

>>.

Q. Hide errors?

2>/dev/null.

Q. Join commands?

Pipe |.

Mid level level

Q. Both streams to one file?

> f 2>&1.

Q. Save and show?

tee.

Q. Stop $ expanding in here-doc?

Quote the word.

Q. Diff two command outputs?

diff <(a) <(b).

Senior level

Q. Why does 2>&1 > f fail?

2 copies the old target of 1.

Q. Pipe and subshell?

Each part may run in a subshell; vars are lost.

Q. Log a whole script?

exec > >(tee -a log) 2>&1.

Q. Safe in-place edit?

Temp file and atomic mv.

Try at home

  1. Use a here-doc to create a shopping list file, then pipe it through sort and tee into a sorted copy.
  2. Compare the list of files in two folders with diff <( ) <( ).

Practice Tasks

About this scenario

London Legal LLP is a law business in London. Oliver Hughes looks after its IT and cloud work. They count money in GBP and run their AWS resources in the eu-west-2 Region. In the tasks below you do real jobs for them using what this lesson teaches. All names, account IDs, IP addresses and numbers are made up for practice.

0 / 21

Easy Save and show

Why: BIZ pipelines move output between many commands.

What to do: Oliver Hughes at London Legal LLP runs df -h. Show the output and also save it to disk.txt.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Append with tee

Why: BIZ pipelines move output between many commands.

What to do: Show uptime output and append it to health.log.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Discard errors

Why: BIZ pipelines move output between many commands.

What to do: Find files named hosts under /etc without crossing into other filesystems.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Count lines

Why: BIZ pipelines move output between many commands.

What to do: Count how many lines in app.log contain ERROR.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Sort numbers

Why: BIZ pipelines move output between many commands.

What to do: Sort sizes.txt numerically, biggest first.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Count users

Why: BIZ pipelines move output between many commands.

What to do: Count the lines of /etc/passwd.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Translate case

Why: BIZ pipelines move output between many commands.

What to do: Convert the contents of names.txt to upper case with tr and print.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Unique lines

Why: BIZ pipelines move output between many commands.

What to do: Print the unique lines of list.txt in sorted order with one sort option.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy First 10

Why: BIZ pipelines move output between many commands.

What to do: Show the first 10 lines of access.log.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Top values

Why: BIZ pipelines move output between many commands.

What to do: From ips.txt print the 3 most repeated lines with counts.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Here-string

Why: BIZ pipelines move output between many commands.

What to do: Send the text 5+7 into bc using a here-string.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Read lines

Why: BIZ pipelines move output between many commands.

What to do: Read servers.txt line by line into variable h and echo each.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium xargs

Why: BIZ pipelines move output between many commands.

What to do: Delete the files listed in old.txt using xargs, doing nothing if the list is empty.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Column total

Why: BIZ pipelines move output between many commands.

What to do: Print the total of the second column of bills.txt with awk.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium First lines of ps

Why: BIZ pipelines move output between many commands.

What to do: Show the 5 processes using most memory with ps sorted.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Read compressed

Why: BIZ pipelines move output between many commands.

What to do: Count ERROR lines in app.log.gz without unzipping to disk.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Second field

Why: BIZ pipelines move output between many commands.

What to do: Print the second whitespace column of every line of data.txt with awk.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Process substitution

Why: BIZ pipelines move output between many commands.

What to do: Compare the sorted contents of a.txt and b.txt with diff without temp files.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Only in first

Why: BIZ pipelines move output between many commands.

What to do: Print lines only in old.txt and not in new.txt with comm and sorted inputs.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Write as root

Why: BIZ pipelines move output between many commands.

What to do: Write the text 1 into /proc/sys/net/ipv4/ip_forward as root via tee.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Loop over command

Why: BIZ pipelines move output between many commands.

What to do: Loop over the lines of the output of ls into variable f without a pipe subshell and echo each.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

In short

  1. 0 in, 1 out, 2 errors.
  2. > replace, >> append.
  3. > f 2>&1 or &> f for both.
  4. | joins, tee saves and shows.
  5. <<'EOF' writes files as-is.
  6. <( ) acts like a file.

Revision, comparison and interview cards

Revise this lesson in a few minutes. Save the cards on your phone or download them as PNG files. The QR code on each card opens this lesson.

0, 1, 2 streams. 0 stdin, 1 stdout, 2 stderr. Save output. Both to one file. Show and save.
Revision card: 0, 1, 2 streams · Download PNG · Open card page
Here-doc and here-string. Many lines into a command. One string in. Command as a file. Quote EOF to stop expansion.
Revision card: Here-doc and here-string · Download PNG · Open card page
pipe vs <( ). Output into stdin. / Output as a file name.
Comparison: pipe vs <( ) · Download PNG · Open card page
<<EOF vs <<<. Many lines. / One string.
Comparison: <<EOF vs <<< · Download PNG · Open card page
Why order of 2>&1 matters?. Redirects run left to right. 2>&1 copies where 1 points now. Right.
Interview card: Why order of 2>&1 matters? · Download PNG · Open card page
Variable empty after a pipe loop?. Pipe runs the loop in a subshell. Feed it with < <( ). Or shopt -s lastpipe.
Interview card: Variable empty after a pipe loop? · Download PNG · Open card page
sudo echo > file fails?. Your shell does the redirect. Use tee as root. Append: tee -a.
Interview card: sudo echo > file fails? · Download PNG · Open card page
<<EOF vs <<'EOF'?. Unquoted expands $vars. Quoted keeps text exact. <<- strips leading tabs.
Interview card: <<EOF vs <<'EOF'? · Download PNG · Open card page

More cards: Ravi Sir's Cards.