Read

Module 10 · Putting it all together

xargs in depth

Turn lists into command arguments safely: batching with -n, one-at-a-time with -I, empty input with -r, and file names with spaces with find -print0 and xargs -0.

What you will learn

  • Explain why `xargs` is needed for commands that do not read standard input.
  • Control batching and placement with `-n` and `-I`.
  • Handle file names with spaces using `find -print0 | xargs -0`.

Pipes feed standard input. That works for grep, sort or wc, which read their input. But rm, mkdir, cp or ls ignore standard input completely; they only look at their arguments. find . -name '*.tmp' | rm does nothing useful. xargs is the adapter: it reads words from its standard input and appends them as arguments to the command you give it. You met it briefly in lesson 37; this lesson covers the options that make it safe.

~% printf 'a\nb\nc\n' | xargs echo rm
rm a b c
~% echo "1 2 3 4 5" | xargs -n 2 echo
1 2
3 4
5
~% printf 'a.txt\nb.txt\n' | xargs -t -n 1 echo
echo a.txt
a.txt
echo b.txt
b.txt

By default xargs splits its input on any whitespace (spaces and newlines) and packs as many words as fit into one command line, which is fast: one rm for a thousand files instead of a thousand rm. -n N limits each run to N arguments. -t prints each command to standard error before running it, a great way to see what will happen. Putting echo in front of the real command is the classic dry run: read the output, then remove the echo.

Putting the argument in the middle: -I

Normally the words go at the end of the command. With -I{} each input line becomes one run, and every {} in the command is replaced with that line. This is how you copy each listed file into a directory, or build a new name from an old one. Because it works line by line, spaces inside a line are kept.

~% cat list.txt
/etc/hostname
/etc/fstab
~% xargs -I{} cp {} backup/ < list.txt
~% ls *.log | xargs -I{} mv {} {}.old

Spaces in file names: -print0 and -0

The default splitting is a trap with names like old file.tmp: xargs sees two words, old and file.tmp, and rm tries to delete two files that do not exist. The robust fix is to separate names with the NUL character, the one byte that can never appear in a file name. find -print0 writes names that way, and xargs -0 reads them that way. Make it a reflex whenever find feeds xargs.

~% find cache -name '*.tmp' | xargs rm
rm: can't remove 'cache/old': No such file or directory
rm: can't remove 'file.tmp': No such file or directory
~% find cache -name '*.tmp' -print0 | xargs -0 rm
~% find cache -name '*.tmp'
~% 
OptionEffect
-n NAt most N arguments per run
-I{}One run per line, {} replaced by the line
-0Input separated by NUL (pair with find -print0)
-rDo not run at all if the input is empty
-tPrint each command before running it
-P NRun up to N commands in parallel
-a FILERead the list from FILE instead of stdin

Commands in this lesson

CommandWhat it does
… | xargs cmdAppend input words as arguments of cmd.
xargs -n 2 cmdAt most two arguments per run.
xargs -I{} cp {} dir/One run per line, placeholder anywhere.
find … -print0 | xargs -0 cmdSafe with spaces in names.
xargs -r cmdSkip the run on empty input.
xargs -t cmdShow each command as it runs.

Quiz

  1. Why does `find . -name '*.tmp' | rm` not delete anything?

    • `rm` takes file names as arguments and ignores standard input.
    • `find` cannot be piped.
    • The quotes around `*.tmp` are wrong.
  2. What does `echo 1 2 3 4 5 | xargs -n 2 echo` print?

    • One line: `1 2 3 4 5`
    • Three lines: `1 2`, `3 4`, `5`
    • Two lines: `1 2` and `3 4`
  3. Which pipeline copes with a file called `my notes.txt`?

    • `find . -name '*.txt' | xargs ls -l`
    • `find . -name '*.txt' -print0 | xargs -0 ls -l`
    • `find . -name '*.txt' | xargs -n 1 ls -l`
  4. What does `-I{}` do?

    • Runs the command once per input line, replacing `{}` with the line
    • Ignores the input
    • Reads input from a file called `{}`
  5. How do you preview what a destructive xargs command would do?

    • Put `echo` before the command, or use `-t`
    • Use `-r`
    • There is no way

Practice

  1. Delete every `.tmp` file below `/root/lab/l94/cache` (some names contain spaces, one is in a subdirectory) using `find` and `xargs`. Leave the other files alone.

  2. `/root/lab/l94/list.txt` contains one file path per line. Copy each of those files into `/root/lab/l94/backup/` with `xargs -I`.

  3. `/root/lab/l94/words.txt` holds six colour names on one line. Write them two per line into `/root/lab/l94/pairs.txt` (three lines, `red green` first).

Open this lesson in the app to do the tasks in a real Linux machine and have them checked.