xa-cli 1.0.0

A modern, safe replacement for xargs
xa-cli-1.0.0 is not a library.

xa

A modern, safe replacement for xargs.

xa reads items from stdin and runs a command for each one. The key difference from xargs: it uses null-delimited input by default, which means filenames with spaces, newlines, or special characters work correctly without extra flags.

Installation

cargo install xa-cli

The binary installs as xa. Or download a pre-built binary from the releases page.

Shell completions (bash, zsh, fish) and a man page are included in each release.

Quick start

# Find files and process them (pairs naturally with fd's -0 flag)
fd -0 '\.png$' | xa -- cwebp {} -o {.}.webp

# Parallel processing with 4 workers
fd -0 '\.log$' | xa -j4 -- gzip {}

# Preview what would run without executing
fd -0 '\.tmp$' | xa --dry-run -- rm {}

Why xa instead of xargs?

xargs xa
Default delimiter whitespace null (\0)
Safe with spaces in filenames only with -0 always
Placeholders {} only {} {/} {.} {/.} {ext} {#} {slot}
Parallel workers -P n -j n
Ordered output in parallel no -k
Retry on failure no --retry n
Rate limiting no --rate n
JSON structured logging no --json-log
Dry run no --dry-run
Progress bar no -p

The null-delimiter default is the most important difference. With xargs, a file named my photo.jpg silently becomes two arguments — my and photo.jpg. With xa, it works correctly out of the box as long as your input source also uses null delimiters (which find -print0, fd -0, and git ls-files -z all support).

Placeholders

When a placeholder appears in the command, xa substitutes it for each input item. If no placeholder is present, the item is appended as a trailing argument.

Placeholder Expands to Example input Result
{} the item as-is /path/to/photo.png /path/to/photo.png
{/} basename /path/to/photo.png photo.png
{.} path without extension /path/to/photo.png /path/to/photo
{/.} basename without extension /path/to/photo.png photo
{ext} extension only /path/to/photo.png png
{#} 1-based item index 1, 2, 3, …
{slot} 0-based worker slot 0, 1, … (with -j)
# Convert images, preserving directory structure
fd -0 '\.png$' | xa -- cwebp {} -o {.}.webp

# Show only filenames
fd -0 | xa -- echo {/}

# Rename by extension
fd -0 '\.jpeg$' | xa -- mv {} {.}.jpg

Parallelism

# Run 8 workers
fd -0 '\.mp4$' | xa -j8 -- ffmpeg -i {} {.}.mkv

# Keep output in input order even when running in parallel
fd -0 | xa -j4 -k -- process {}

# Stop immediately if any command fails
fd -0 | xa -j4 --halt-on-error -- risky-command {}

Batching

-n <N> passes N items per command invocation instead of one at a time.

# Pass 10 files to each invocation of a command
fd -0 '\.txt$' | xa -n10 -- wc -l

# All items in a single invocation (-n0)
fd -0 | xa -n0 -- tar czf archive.tar.gz

With a placeholder, each item's args are repeated within the single invocation:

# xa -n2 -- echo {} expands to: echo item1 item2, then echo item3 item4, ...
printf 'a\0b\0c\0d\0' | xa -n2 -- echo {}
# a b
# c d

Input modes

# Default: null-delimited (safest)
printf 'foo\0bar\0' | xa -- echo {}

# Newline-delimited
printf 'foo\nbar\n' | xa -l -- echo {}

# Custom delimiter
printf 'foo:bar:baz' | xa -d: -- echo {}

# Whitespace-split with quoting (legacy xargs behaviour)
printf "'hello world' bye" | xa -s -- echo {}
# hello world
# bye

Resilience

# Retry failed commands up to 3 times with exponential backoff
xa --retry 3 -- flaky-api-call {}

# Stop after the first failure
xa --halt-on-error -- critical-step {}

# Limit to 10 commands per second
xa --rate 10 -- api-call {}

# Confirm before each command
xa --confirm -- rm {}

Output options

# Print each command before running it
xa --verbose -- make -C {}

# Prefix each output line with the source item
xa --tag -- grep pattern {}

# Emit structured JSON to stderr for each completed command
xa --json-log -- process {} 2>log.ndjson

# Show a progress bar
xa -p -j4 -- encode {}

Shell mode

--shell (-S) wraps the command in sh -c, enabling pipes and other shell features:

fd -0 '\.log$' | xa --shell -- 'grep ERROR {} | wc -l'

Comparison with similar tools

  • xargs — the classic. Whitespace-splitting default makes it unsafe with most filenames. xa is a drop-in improvement for the common find | xargs pattern.
  • GNU parallel — extremely feature-rich but a large Perl dependency. xa covers the most common 90% of use cases in a single ~2 MB static binary.
  • fd's built-in exec (fd --exec) — convenient for single commands but no parallelism control, no retry, no rate limiting, no JSON logging.

Benchmarks

Benchmarks run on a Linux x86-64 machine with cargo bench (criterion). These measure wall-clock time for process spawning + execution, so they reflect real-world overhead.

Workload Throughput
Sequential echo — 100 items, 1 worker ~3,350 items/s
Parallel echo — 100 items, 4 workers ~12,380 items/s
Batch echo — 100 items, -n10 (10 invocations) ~28,800 items/s
Dry-run — 1,000 items, placeholder expansion only ~550,000 items/s

Parallelism (-j4) gives roughly a 3.7× throughput improvement for CPU-bound or I/O-bound workloads. Batch mode (-n10) reduces process-spawn overhead by ~8.6× for commands that accept multiple arguments. The dry-run numbers show that placeholder expansion itself adds negligible overhead (~1.8 µs per 1,000 items).

Run benchmarks yourself:

cargo bench
# HTML report: target/criterion/report/index.html

Exit codes

Code Meaning
0 all commands succeeded
1 one or more commands failed
2 xa itself failed (bad arguments, I/O error)
130 interrupted by Ctrl-C