# Tee and Xargs: Splitting and Feeding

tee saves what flows through a pipe into a file and passes it on, so you can keep a report and read it at once, and xargs turns the lines it reads into arguments for commands such as mkdir and rm, which don't read a pipe.

Journeyman · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Save a pipeline's output in a file and still see it with tee, and add to a file with tee -a
- Run a command on the lines a pipeline prints with xargs, checking first with xargs echo

## Key takeaways

- tee saves what comes through a pipe into a file and also passes it on, to the screen or the next command. tee -a adds to the end of the file instead of replacing it.
- Commands such as mkdir and rm take names as arguments and ignore a pipe. xargs reads the lines and runs the command with them as its arguments.
- Put echo in front of the command, as in xargs echo rm, to see the command xargs would run without running it.
- xargs splits at spaces as well as at line ends, so a file name with a space becomes two names.

Bea's accountant wants the city ranking in a file, and Bea wants to read it now, too. With `>` she gets the file and a blank screen, and then has to `cat` it. And her `receipts` folder holds a receipt for every birthday cake, a few of them for orders that were canceled: she wants exactly those receipts gone. `grep -l` finds them, but `rm` doesn't read a pipe. Two more plumbing fittings solve both: `tee` splits the flow in two, and `xargs` feeds lines to a command that wants names. IT techs and developers use them to keep a record of a job while watching it, and to act on exactly the files a search found.

The examples use the bakery folder from *Pipes: |* (its setup lines make it), and `cities.txt` from *Count, Sort, Remove Duplicates: wc, sort and uniq*. Go into the folder with `cd ~/terminal-practice/bakery`.

## tee: save it and see it

A T-shaped joint in a water pipe splits the flow two ways. `tee` (read "tee", like the letter T) does the same with lines: it writes them into a file and passes them on:

```bash
cut -d, -f5 orders.csv | tail -n +2 | sort | uniq -c | sort -rn | tee city-report.txt
```

```text
    212 Portland
     96 Salem
     89 Bend
     73 Eugene
     27 Medford
     23 Corvallis
```

The ranking showed on the screen, and the same six lines are in the file:

```bash
wc -l city-report.txt
```

```text
6 city-report.txt
```

![A T-shaped pipe joint. Lines from sort | uniq -c | sort -rn flow in from the left; at the joint, labeled tee city-report.txt, the flow splits: one branch goes up into a file labeled city-report.txt, and the other carries on to the right to a screen showing 212 Portland.](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/11/4a0efb38f2394669a61b0e9d90c7b586.webp)

Like `>`, `tee` replaces the file each time. `tee -a` (for *append*) adds to its end, like `>>`, so this adds the bestsellers under the cities:

```bash
cut -d, -f6 orders.csv | tail -n +2 | sort | uniq -c | sort -rn | tee -a city-report.txt
wc -l city-report.txt
```

```text
    175 sourdough loaf
    100 croissant
     80 blueberry muffin
     78 baguette
     68 cinnamon roll
     19 birthday cake
12 city-report.txt
```

Because `tee` passes its lines on, it can also sit in the middle of a pipeline and save a stage on the way past. This saves the mailing list from *Columns and Characters: cut and tr* and counts it, in one line:

```bash
cut -d, -f4 orders.csv | tail -n +2 | tr 'A-Z' 'a-z' | sort -u | tee mailing-list.txt | wc -l
```

```text
20
```

## xargs: lines into arguments

Bea wants a folder for each city, to keep its delivery notes in. `sort -u cities.txt` lists the six cities, but `mkdir` takes folder names as **arguments**, the words after its name, and never reads a pipe:

```bash
sort -u cities.txt | mkdir -p
```

```text
error: the following required arguments were not provided:
  <dirs>...

Usage: mkdir [OPTION]... DIRECTORY...

For more information, try '--help'.
```

However it's worded on your system, the message means the same thing: `mkdir` got no folder names. `xargs` (read "x args") reads the lines that come in, puts them after a command as its arguments and runs it. Before you let it run anything, put `echo` in front of the command. `xargs` then prints the command it would run, and runs nothing:

```bash
sort -u cities.txt | xargs echo mkdir -p
```

```text
mkdir -p Bend Corvallis Eugene Medford Portland Salem
```

That's the command you'd have typed by hand. Take `echo` away to run it:

```bash
sort -u cities.txt | xargs mkdir -p
ls -d */
```

```text
Bend/  Corvallis/  Eugene/  Medford/  Portland/  Salem/
```

## Delete exactly what a search found

These lines make the `receipts` folder, with a receipt for each of seven cake orders, then mark two of them canceled. The `for` line repeats one `echo` for each order number; *For Every File: for Loops*, in Module 5, teaches loops. They're safe to run again.

```bash
mkdir -p receipts
for n in 1025 1054 1103 1185 1463 1499 1514; do echo "Order $n: birthday cake, PAID" > receipts/cake-$n.txt; done
echo "Order 1103: birthday cake, CANCELED" > receipts/cake-1103.txt
echo "Order 1463: birthday cake, CANCELED" > receipts/cake-1463.txt
ls receipts
```

```text
cake-1025.txt  cake-1103.txt  cake-1463.txt  cake-1514.txt
cake-1054.txt  cake-1185.txt  cake-1499.txt
```

`rm` deletes for good, so follow the habit from *Deleting Safely: rm and rmdir*: look first. `grep -l` (from *Finding Text: grep*) lists the files that contain a word:

```bash
grep -l CANCELED receipts/*
```

```text
receipts/cake-1103.txt
receipts/cake-1463.txt
```

Then see the command `xargs` would build:

```bash
grep -l CANCELED receipts/* | xargs echo rm
```

```text
rm receipts/cake-1103.txt receipts/cake-1463.txt
```

Exactly the two canceled receipts. Now run it, and check what's left:

```bash
grep -l CANCELED receipts/* | xargs rm
ls receipts
```

```text
cake-1025.txt  cake-1054.txt  cake-1185.txt  cake-1499.txt  cake-1514.txt
```

One warning: `xargs` splits what it reads at spaces as well as at line ends, so a file named `cake order.txt` would reach `rm` as two names, `cake` and `order.txt`. When names might have spaces, let `find` end each name with an invisible marker, `-print0`, and tell `xargs` to split only there with `-0`: `find receipts -name "*.txt" -print0 | xargs -0 ls`.

## Check yourself

2 questions to try on the lesson page.

## So what

With `tee` you never have to choose between saving a result and seeing it, and with `xargs` any list a pipeline makes can drive any command: make, move or delete exactly the files you found.

## Key terms

- [tee](https://www.ask-codey.com/glossary/tee)
- [xargs](https://www.ask-codey.com/glossary/xargs)
- [Argument](https://www.ask-codey.com/glossary/argument-unix)

## Cheat sheets

- [Tee and Xargs: Splitting and Feeding](https://www.ask-codey.com/cheat-sheets/bash-tee-and-xargs-cheat-sheet)

## Related

- [Columns and Characters: cut and tr](https://www.ask-codey.com/lessons/bash-cut-and-tr)
- [Deleting Safely: rm and rmdir](https://www.ask-codey.com/lessons/bash-deleting-safely)

Source: https://www.ask-codey.com/lessons/bash-tee-and-xargs
