Hardscrabble 🍫

By Max Jacobson

See also: the archives and an RSS feed


January 16, 2023

This is a quick tribute to and summary of xargs, the glue that holds together most of my shell scripts.

Imagine you have a file called fruits.txt:

$ cat fruits.txt

It’s a file with fruits all listed out on separate lines. xargs lets you squash that list down into a single line:

$ cat fruits.txt | xargs
apples.txt oranges.txt bananas.txt

What it’s doing is taking a list of things and turning them into arguments.

What do I mean by “arguments”? Let’s look at this example…

$ rm apples.txt oranges.txt bananas.txt

In that statement, we’re passing three arguments to the rm command, which will remove the files. In order to pass multiple file names to rm, they need to be written as arguments, meaning they’re all on one line and separated by spaces.

So… what if we have a fruits.txt that contains a list of filenames, and we want to execute the rm program, and pass it that list of files as arguments?

We can do it like this:

$ cat fruits.txt | xargs rm

In English, you might read this as “Take the contents of fruits.txt, turn that into arguments, and then execute the rm command with those arguments”.

This kind of problem comes up all the time when writing shell scripts. For me, it comes up most often when I’m writing one liners that I’m executing at the command line. Here are a few real examples I found in my shell history:

$ git ls-files | grep -E "\.(rb|jbuilder|ru)$" | xargs rubyfmt --write

In other words: “Find all of the ruby files in my git repository and format them with rubyfmt.”

$ git ls-files app/assets/javascripts/checkout | grep -E "\.js$" | xargs code

In other words: “Find all of the JavaScript files in a particular folder of my git repository and open them in VS Code.”

$ git ls-files | grep "test.jsx" | xargs grep -l  enzyme | xargs rm

(This one has a double xargs 👀)

In other words: “Find all of the react tests in my git repository and then search just those files for the word enzyme, and then print out the list of those file names, and then pass them as arguments to the rm command.

That last example is a pretty good category of problem that xargs solves. I often search through a codebase to find source code that matches a pattern. It’s easy enough to do that with a simple grep (or the search in your preferred editor). But I occasionally want to narrow down the search to a subset of files, and that’s not always easy to do with a single statement.

For example, I might want to search for “Find me everywhere in this codebase that contains the string FOO but narrowed down to files that happen to also contain the string BAR”. That’s easy enough to achieve once you’re comfortable with xargs:

$ grep -lr BAR . | xargs grep FOO
./app/models/human.rb:    puts FOO.inspect

Note: in my day-to-day life, I’m generally using ripgrep, not grep, but I wanted to keep the examples simple by using more standard things. But once you’re using ripgrep, the command gets a bit simpler and faster too:

$ rg -l BAR | xargs rg FOO
22:    puts FOO.inspect

All right, that’s all I got. Have fun.

Note: I don't have comments or analytics on this website, so it's hard to tell if people are reading or enjoying it. Please feel free to share any feedback or thoughts by shooting me an email or tagging me in a post on Mastodon @maxjacobson@mastodon.online.