tee and xargs

tee: save it and see it

tee is a T-junction in a pipeline: it writes what it receives into a file and passes it on, so you still see it:

ls /etc | head -n 3 | tee first3.txt
cat first3.txt

In the terminal

student@vulntech:~$ ls /etc | head -n 3 | tee first3.txt
acpi
alpine-release
apk
student@vulntech:~$ cat first3.txt
acpi
alpine-release
apk

tee -a appends instead of replacing, like >>:

echo "first check" | tee -a checks.log
echo "second check" | tee -a checks.log
cat checks.log

In the terminal

student@vulntech:~$ echo "first check" | tee -a checks.log
first check
student@vulntech:~$ echo "second check" | tee -a checks.log
second check
student@vulntech:~$ cat checks.log
first check
second check

Writing to a protected file: sudo tee

Here's a puzzle. sudo runs a command as the administrator, yet this fails:

sudo echo "Welcome aboard" > /etc/motd

In the terminal

student@vulntech:~$ sudo echo "Welcome aboard" > /etc/motd
-bash: /etc/motd: Permission denied

The redirection > is done by your shell, as you, before sudo even starts, and you can't write to /etc/motd. The fix is to let a program running as root do the writing: pipe the text into sudo tee.

echo "Welcome aboard" | sudo tee /etc/motd
cat /etc/motd

In the terminal

student@vulntech:~$ echo "Welcome aboard" | sudo tee /etc/motd
[sudo] password for student:
Welcome aboard
student@vulntech:~$ cat /etc/motd
Welcome aboard

Tip: sudo tee -a file adds to the end of a protected file. Add > /dev/null if you don't want to see the text twice.

xargs: lines become arguments

Some commands, such as mkdir, rm and touch, don't read standard input: they want names as arguments. xargs reads lines and adds them to the end of a command:

cat dirs.txt
cat dirs.txt | xargs mkdir
ls -d */

In the terminal

student@vulntech:~$ cat dirs.txt
maps
logs
photos
student@vulntech:~$ cat dirs.txt | xargs mkdir
student@vulntech:~$ ls -d */
logs/  maps/  photos/

A classic: find files, then do something to all of them at once:

find cache -name '*.tmp'
find cache -name '*.tmp' | xargs rm
find cache -type f

In the terminal

student@vulntech:~$ find cache -name '*.tmp'
cache/old/c.tmp
cache/a.tmp
cache/b.tmp
student@vulntech:~$ find cache -name '*.tmp' | xargs rm
student@vulntech:~$ find cache -type f
cache/keep.txt

Useful xargs options

OptionWhat it does
-n 1Run the command once per item
-I {}Put each line wherever {} appears, instead of at the end
-0Items are separated by a null character: use with find -print0, it's safe with spaces in file names
echo a b c | xargs -n 1 echo item:
ls *.txt | xargs -I {} cp {} {}.bak
ls

In the terminal

student@vulntech:~$ echo a b c | xargs -n 1 echo item:
item: a
item: b
item: c
student@vulntech:~$ ls *.txt | xargs -I {} cp {} {}.bak
student@vulntech:~$ ls
crew.txt  crew.txt.bak  route.txt  route.txt.bak

Common mistake: Plain xargs splits names at spaces, so my notes.txt becomes two names. If file names may contain spaces, use find ... -print0 | xargs -0 ....

Remember: tee file saves and shows. sudo tee writes protected files. xargs turns lines into arguments.

HOME LEARN COMMUNITY DASHBOARD
Practice terminal Starting…