Skip to content
Dev Tools Article

Why Developers Keep Rewriting xargs

A weekend bash project hit the HN front page and reopened the shell's oldest argument: how to loop safely.

Lenn Voss
Lenn Voss
Cloud & Infrastructure Writer · Jul 21, 2026 · 4 min read
Why Developers Keep Rewriting xargs

A post titled "I wrote an bash enumerator because I was sick of xargs" hit the Hacker News front page yesterday — 150 points, 128 comments — and the comments barely discuss the tool. They're a referendum on shell iteration itself: find -exec versus piping to xargs, POSIX purity versus GNU extensions, whether anyone alive fully understands OpenBSD's -J flag. One commenter's entire response was "Have you seen parallel?" Another just linked xkcd 927, the standards one.

That reaction is the story. bashumerate is a fine weekend project, but it's roughly the hundredth attempt to replace a tool that's been annoying people since the early 1980s. The interesting question isn't whether this one sticks (it won't). It's why xargs keeps generating replacements — and what you should actually be typing instead.

The pitch

bashumerate ships a single command, enumerate, that gives files, lines, numeric ranges, and literal lists one consistent interface with one placeholder:

enumerate -f '*.sh' -- 'wc -l {}'          # files matching a glob
cat urls.txt | enumerate -l - -- 'curl -s {}'   # lines from stdin
enumerate -r 1 10 -- 'printf "%02d\n" {}'  # a numeric range
enumerate -L staging prod -- 'deploy {}'   # a literal list

There's --parallel with a -P job limit, --include/--exclude filters, --dry-run, NUL-delimited I/O, and a genuinely nice extension mechanism: drop a function in lib/enumerators/ and you get a new source, so enumerate docker -- 'docker restart {}' works after a five-line plugin. It's pure bash, MIT-licensed, and currently at v0.2.2-hotfix002 with about 40 stars — which tells you where it is on the maturity curve.

The author's pitch is that for loops, find -exec, and xargs each have their own syntax and quirks, and you shouldn't need to remember which one splits on whitespace. That complaint is real. The solution has problems.

Read the benchmark fine print

The README claims enumerate is 12–27x faster than xargs, and the numbers in the repo's own bench/results.txt check out: iterating 10,000 files took xargs 7.16 seconds against enumerate's 0.62. But the comparison is against xargs -I{} — the mode that forks one process per item, which is xargs at its slowest and least idiomatic. Plain xargs batches thousands of arguments into a single invocation; on that same workload it would finish before enumerate parsed its flags. And the repo's own data shows a bare bash for loop running the 100,000-item test in 0.17 seconds where enumerate needs about 5. The README admits this in a quieter paragraph: on no-op commands, even speed mode is ~17x slower than a plain loop.

So the honest headline is "running commands in-process beats forking per item" — true, and also exactly what a for loop already does.

How does it run things in-process? eval. Credit where due: the implementation is more careful than that word suggests. It rewrites your {} to a quoted variable reference before evaluating, so a filename like ; rm -rf ~ expands as data rather than being re-parsed as shell. But every per-item command runs with || true appended, meaning a failing item is silently swallowed. GNU xargs exits 123 if any invocation fails — dozens of CI pipelines you rely on depend on that exact behavior. A replacement that loses failure propagation isn't a replacement; it's a demo.

The grievance is legitimate

None of this means the itch is fake. xargs's default input parsing splits on any whitespace and interprets quotes and backslashes — behavior so hazardous that GNU findutils' own documentation steers you to -0 for anything real. The flag surface is a museum: -I versus -n versus -L, BSD's extra -J, subtle GNU/BSD divergence on almost all of them. One HN commenter described reading the OpenBSD man page and having their "brain glaze over," and the thread's most-repeated advice was five different incantations for the same operation. When the community's consensus answer is "memorize this specific pipe with these specific flags," the tool's interface has failed, even if its semantics — batching to ARG_MAX, parallel dispatch, meaningful exit codes — are exactly right.

That's the pattern behind every xargs alternative of the past decade: GNU parallel (powerful, Perl, famously nags for citations), Leah Neukirchen's minimalist xe, the -x/-X exec flags built into fd. They fix the syntax. The ones that survive are the ones that kept the semantics.

What to actually type

For the 95% case, you don't need a new tool — you need three idioms, learned once:

# Batched, parallel, NUL-safe — the workhorse
find . -name '*.log' -print0 | xargs -0 -P"$(nproc)" gzip

# No pipe at all — find batches like xargs does
find . -name '*.log' -exec gzip {} +

# When you need real shell logic per item
while IFS= read -r -d '' f; do
  gzip "$f" && mv "$f.gz" archive/
done < <(find . -name '*.log' -print0)

If you want a friendlier interface with maintained code, fd -e log -X gzip batches like -exec + and -x gives you parallel per-item execution — fd is in every package manager and isn't going anywhere. For job-server-grade parallelism across machines, GNU parallel remains the heavyweight answer.

The verdict

bashumerate is a good learning artifact and its pluggable-source design is a genuinely nice idea that bigger tools could steal. But don't put a 40-star, eval-based, failure-swallowing v0.2 in your scripts. The lesson of yesterday's thread is older and more useful: xargs endures not because its interface is good — it's terrible — but because its semantics encode thirty years of edge cases the replacements keep rediscovering one hotfix at a time. Learn the three idioms. They're portable, they're safe, and they'll outlive this tool, and probably the next five like it.

Sources & further reading

  1. I wrote an bash enumerator because I was sick of xargs — numerlab.org
  2. I wrote an bash enumerator because I was sick of xargs (discussion) — news.ycombinator.com
  3. wallach-game/bashumerate — github.com
Lenn Voss
Written by
Lenn Voss · Cloud & Infrastructure Writer

Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.

Discussion 2

Join the discussion

Sign in or create an account to comment and vote.

Pia Andersson @promptsmith_pia · 40 minutes ago

xargs edge cases live rent-free in my head. curious if this handles null-delimited input + signal handling better than the usual suspects

Tess O'Brien @typescript_tess · 2 hours ago

i get the frustration, but 'xargs is too hard to use safely' isn't really the problem xargs is solving — it's solving the problem of fitting infinite argument lists into finite exec buffers. every replacement i've seen just papers over that with a language that has better types and error handling, which... yeah, that's why i don't write bash scripts past a certain complexity threshold. pick a real language.

Related Reading