# 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
```sh
cargo install xa-cli
```
The binary installs as `xa`. Or download a pre-built binary from the [releases page](https://github.com/welcomevideogame/xa/releases).
Shell completions (bash, zsh, fish) and a man page are included in each release.
## Quick start
```sh
# Find files and process them (pairs naturally with fd's -0 flag)
# Parallel processing with 4 workers
# Preview what would run without executing
## Why xa instead of xargs?
| 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.
| `{}` | 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`) |
```sh
# Convert images, preserving directory structure
# Show only filenames
# Rename by extension
## Parallelism
```sh
# Run 8 workers
# Keep output in input order even when running in parallel
# Stop immediately if any command fails
## Batching
`-n <N>` passes N items per command invocation instead of one at a time.
```sh
# Pass 10 files to each invocation of a command
# All items in a single invocation (-n0)
With a placeholder, each item's args are repeated within the single invocation:
```sh
# 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
```sh
# Default: null-delimited (safest)
printf 'foo\0bar\0' | xa -- echo {}
# Newline-delimited
printf 'foo\nbar\n' | xa -l -- echo {}
# Custom delimiter
# Whitespace-split with quoting (legacy xargs behaviour)
# bye
```
## Resilience
```sh
# 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
```sh
# 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:
```sh
## 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.
| 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:
```sh
cargo bench
# HTML report: target/criterion/report/index.html
```
## Exit codes
| `0` | all commands succeeded |
| `1` | one or more commands failed |
| `2` | xa itself failed (bad arguments, I/O error) |
| `130` | interrupted by Ctrl-C |