Connect commands

INPUT · Slides

Turn a stream into arguments (`xargs`)

01 / 08

Some things a pipe cannot reach

You have joined everything with pipes so far, but there are things it does not work on.

find . -name '*.tmp' | rm      # nothing gets removed

The reason is how rm is built. rm does not read the way in. What to remove has to come as arguments.

rm old/a.tmp old/b.tmp     # handed over as arguments

Something has to turn what came down the pipe into the shape of arguments. That something is xargs.

02 / 08

Tools that read the way in, and tools that do not

Split the tools into two groups in your head.

Tools that read the way in (a pipe reaches them as they are)

  • grep sort wc head tail uniq tr less tee

Tools that only take arguments (they need xargs)

  • rm cp mv mkdir touch chmod echo

There is a way to tell. Tools that handle files are on the argument side, tools that work on contents are on the way in side.

Work on names (remove, create, move) needs xargs. Work on contents (search, count, sort) needs only a pipe. That is a good way to hold it.

03 / 08

xargs does one job

xargs does exactly one thing: take what comes in the way in and lay it out as the arguments of the command written after it.

~ $ echo a.txt b.txt | xargs touch(= it ran touch a.txt b.txt)

It treats newlines and spaces alike as separators, so the output of find or ls (one per line) goes straight in.

find . -name '*.tmp' | xargs rm

"Remove everything I found" is now something you can write.

~ $ find . -name '*.tmp'./old/a.tmp./old/b.tmp~ $ find . -name '*.tmp' | xargs rm

04 / 08

All at once, or one at a time

By default xargs hands things over as many at a time as it can. Even with a hundred, it tries to do it in one command (because that is faster).

When you want them one at a time, that is -n1.

~ $ echo a b c | xargs echoa b c            <- handed over in one go~ $ echo a b c | xargs -n1 echoabc               <- split into three

-n2 is two at a time. You get to decide how many go at once.

05 / 08

-I{} decides where they land

xargs normally hands things over by tacking them on the end. But sometimes you want them in the middle.

cp file file.bak

Here the same name has to go in two places. That is when you use -I{}.

ls *.txt | xargs -I{} cp {} {}.bak

Each {} gets replaced by the line it received. Using -I also makes it one line at a time automatically (the same as -n1).

The {} notation is the same as in find -exec, so that is one thing fewer to learn.

06 / 08

-t shows you what it will run

xargs is a frightening tool, because combined with rm it removes things in bulk.

So learn how to check before you press enter. Add -t and it shows you the command before it runs it.

find . -name '*.tmp' | xargs -t rmrm ./old/a.tmp ./old/b.tmp    <- this is what it is about to run

Safer still is to type the find on its own first, without the xargs. If the list that comes out is only what you meant to remove, then add | xargs rm.

Check, then remove. That order becomes a habit for life.

07 / 08

Watch out for names with spaces in them

xargs has a weakness. It treats a space as a separator, so a name with a space in it breaks.

~ $ lssp ace.txt~ $ ls | xargs wc -cwc: sp: No such file or directorywc: ace.txt: No such file or directory

sp ace.txt was taken for two names.

The fix is fixed. Give find a -print0 and xargs a -0. That tells them to separate on an invisible character (NUL) rather than on whitespace.

find . -name '*.txt' -print0 | xargs -0 wc -c

It looks like an incantation, but hold on to it as the set phrase for handling names with spaces.

08 / 08

Now have a go

There are two .tmp files inside old, for practising removal (this environment rebuilds your home for every question, so remove away).

boxes.txt says spring, summer and autumn. You will use it to practise making boxes in bulk.

There is little to learn about xargs, but combining it goes a long way. "Find things and process the lot" becomes possible.