A small, single-file CLI tool that runs a shell command once per input token,
replacing an explicit placeholder (such as {} or {FILE}) with each token.
It plays a similar role to
xargs, but aims to be:
- more explicit (no guessing about where arguments go),
- safer by default (shell quoting enabled unless you opt out),
- more ergonomic for line-, delimiter-, or NUL-separated inputs.
- Explicit placeholder substitution (default:
{}) - Safe shell-quoting by default via
shlex.quote - Flexible tokenization:
- newline-based (default),
- custom delimiters (
-d), - NUL-delimited input (
-0, likexargs -0).
- Optional whitespace trimming and control over empty tokens
- Sequential or parallel execution (
-P), with stdin-safety guard --dry-runpreview and-t/--tracefor shell-like tracing- Configurable stdin encoding and error handling
- Extra environment variables and custom shell executable
Tip
Think of each as "xargs with an explicit {} placeholder and safer defaults".
- Python 3.8+ (system Python is fine; no virtual environment required)
- POSIX-like shell environment (Linux, macOS, WSL, etc.)
The script has no third-party dependencies.
# Clone the repository
git clone https://github.com/BMTLab/each.git
cd eachOr download each.py directly from the repository (e.g., via GitHub web UI).
chmod +x each.pyYou can now run it as:
./each.py 'echo {}'Rename the script to each and move it somewhere on your PATH, for example:
sudo ln -s <path-to-each>/each.py /usr/local/bin/eachNow you can use:
each 'echo {}'printf '%s\n' a b c | each 'echo {}'
# Output:
# a
# b
# cprintf '%s\n' file1.txt file2.txt | each 'wc -l {}'With NUL-delimited input (safe for weird file names):
find . -type f -name '*.log' -print0 | each -0 'gzip -9 {}'printf '%s\n' a b c | each -p '{ITEM}' 'echo item={ITEM}'
# Output:
# item=a
# item=b
# item=cprintf 'a b\nc d\n' | each -p '{FILE}' --no-quote 'echo {FILE}'
# Output:
# a b
# c dImportant
--no-quote disables shell quoting.
Only use it when you fully control the input and are aware of the risks
(spaces, globbing, shell metacharacters, etc.).
Basic syntax:
each [OPTIONS] 'command with {} placeholder'The command argument is a shell command template containing a placeholder (default {}).
For each input token, all occurrences of that placeholder in command are replaced
with the (optionally quoted) token, and the resulting command is executed.
| Option | Type | Default | Description |
|---|---|---|---|
command |
positional | — | Shell command template containing the placeholder (default {}). |
-p, --placeholder |
string | {} |
Placeholder substring to replace with each token. |
-d, --delimiter |
repeatable string | — | Literal delimiter(s) for splitting input (see below). |
-0, --null |
flag | False |
Treat input as NUL-delimited (\0), like xargs -0. |
--strip |
flag | False |
Strip leading/trailing whitespace from each token. |
--keep-empty |
flag | False |
Keep empty tokens (by default they are dropped). |
-E, --encoding |
string | utf-8 |
Encoding for decoding stdin as text. |
--errors |
string | strict |
Error policy for decoding (strict, replace, surrogatepass, etc.). |
-P, --max-procs |
integer | 1 |
Run up to N commands in parallel. Requires --no-stdin when N > 1. |
--no-stdin |
flag | False |
Do not forward this process's stdin to child processes. Required for parallel mode. |
--dry-run |
flag | False |
Do not execute anything; just print the final command per token. |
-t, --trace |
flag | False |
Print commands before executing them (like xargs -t). |
--no-quote |
flag | False |
Insert tokens as-is instead of quoting them for the shell. |
--env |
repeatable KEY=VALUE |
— | Add or override environment variables for child processes. |
--shell |
string | system default | Path to the shell executable (e.g., /bin/bash). |
Tip
Combine --dry-run and --trace while experimenting:
find . -maxdepth 1 -type f -print0 | each -0 --dry-run -t 'rm {}'This will show you exactly what would be executed without actually deleting anything.
each consumes all of stdin, decodes it according to --encoding
and --errors, and then splits the resulting text into tokens.
If neither -d/--delimiter nor -0/--null are used, input is split using
str.splitlines(), which handles \n, \r\n, and \r transparently.
printf 'foo\nbar\n' | each 'echo {}'You can provide one or more literal delimiters. Internally, they are combined into a single regular expression that splits on any of them.
printf 'foo ; bar ;baz' | each -d ';' --strip 'echo {}'
# Tokens: "foo", "bar", "baz"You can repeat -d:
printf 'a,b;c' | each -d ',' -d ';' 'echo {}'
# Tokens: "a", "b", "c"To interoperate with tools like find -print0 that produce NUL-delimited streams, use -0:
find . -type f -print0 | each -0 'wc -l {}'--striptrims leading and trailing whitespace from each token before deciding whether it is empty.--keep-emptykeeps empty tokens; by default they are skipped.
This allows you to control how consecutive delimiters or trailing delimiters are handled.
For each token:
- The placeholder in the command template is replaced with the (optionally quoted) token.
- The resulting string is executed as a shell command via
subprocess.run(..., shell=True, ...).
With the default -P 1, commands are executed one by one.
The first failing child exit code is propagated as the exit code of each.
printf '%s\n' a b c | each 'echo {}'You can run up to N commands in parallel:
find logs -type f -name '*.log' -print0 \
| each -0 -P 4 --no-stdin 'gzip -9 {}'Important
When -P N is used with N > 1, you must pass --no-stdin.
Otherwise, each will abort with an error to avoid multiple child processes competing for the same stdin.
Warning
Parallel execution does not guarantee ordering of output.
If you need ordered output, use sequential mode (-P 1, the default).
0– success, or nothing to do (no tokens).- Non-zero – the first non-zero exit code returned by any child process.
65– command template does not contain the configured placeholder.66– invalid--enventry (does not look likeKEY=VALUE).67–-P/--max-procs > 1used without--no-stdin.
In error cases, a human-readable message is printed to stderr.
You can extend or override environment variables for child processes:
printf '%s\n' a b | each --env FOO=bar 'echo $FOO:{}'Each --env KEY=VALUE argument is parsed and merged into a copy of the current process environment.
By default, each uses the system default shell for subprocess.run(..., shell=True).
You can explicitly choose a shell:
printf '%s\n' a b | each --shell /bin/bash 'echo ${0}:{}'This can be useful if your system defaults to a different shell, and you rely on specific shell features.
each is a thin, explicit wrapper around your shell:
- It will run exactly the commands you ask it to run.
- By default, tokens are shell-quoted, which protects against many common issues with spaces and simple metacharacters.
- If you disable quoting (
--no-quote), you are fully responsible for ensuring that inputs are safe and properly escaped.
Warning
Do not use each with untrusted input when the command template can modify or delete data.
Treat it like any other shell scripting tool.
For dry runs and debugging, prefer:
... | each --dry-run -t 'your command with {}'This project is licensed under the MIT License.
Important
This tool executes arbitrary commands in your shell. Running it with elevated privileges or against critical data may cause data loss or other damage if used incorrectly. Use it at your own risk. The author and contributors accept no liability for any consequences of using this software.
Issues and pull requests are welcome.
If you would like to propose new flags or behavior, please include concrete examples and a brief rationale
so we can keep each small, understandable, and focused on its core job 😇