Open-Source Wikis

/

fd

/

Features

/

Command execution templates

sharkdp/fd

Command execution templates

Active contributors: tmccombs, sharkdp, Michael Aaron Murphy, Jonah Caplan

Purpose

-x/--exec runs a command per result; -X/--exec-batch runs a command once with all results as arguments. This page is the user-facing companion to systems/command-execution and systems/format-templates. Read those for implementation details.

The two modes

Flag Mode Behaviour
-x, --exec One-by-one Spawns one child per result, in parallel up to --threads.
-X, --exec-batch Batch Spawns one child per batch of results. Multiple invocations occur if the command line would exceed ARG_MAX or --batch-size is hit.
-l, --list-details Synthetic batch Equivalent to -X ls -lhd .... fd picks GNU ls/gls automatically.

-x, -X, and -l are mutually exclusive (declared via clap ArgGroup "execs" in src/cli.rs). They also conflict with --max-results, --quiet, and --max-one-result because exec mode is a "do something for every match" operation.

The placeholder language

Five placeholders, plus {{ / }} for literal braces:

{}    — full path                  (e.g. dir/file.txt)
{/}   — basename                   (file.txt)
{//}  — parent directory           (dir)
{.}   — full path without ext.     (dir/file)
{/.}  — basename without ext.      (file)
{{    — literal {
}}    — literal }

If you don't include any placeholder, fd appends an implicit {} to the end of the command. This is what makes fd -e zip -x unzip work without writing unzip {} explicitly.

In batch mode, each command may use exactly one placeholder. CommandSet::new_batch enforces this; using two distinct tokens is a parse-time error.

Argument boundaries

Everything after -x/-X is consumed as the command template until either end-of-args or a literal \;. The clap declarations use value_terminator(";") and allow_hyphen_values(true) to make this work.

That means:

# This treats `pattern path` as fd args because of the literal `\;`:
fd -x echo \; pattern path

# Whereas this passes `pattern path` to echo:
fd -x echo pattern path

# In practice the recommended form is to put -x last:
fd pattern path -x echo

Multiple -x/-X invocations

-x (and -X) can appear multiple times. Each occurrence is parsed into a separate CommandTemplate, and all of them run for each result (or each batch). Useful for fan-out:

# Convert each .jpg to .png AND log a copy of the path:
fd -e jpg -x convert {} {.}.png -x echo converted: {}

Parallelism

-x runs up to --threads N commands in parallel (default min(available_parallelism, 64)). Use --threads=1 for serial execution; this also disables stdout/stderr buffering so a child can interact with the user's terminal (e.g., fd ... -x vim).

-X is single-process by definition. Each batch's child runs synchronously inside CommandBuilder::finish.

Output ordering and --print0

  • With -x and multiple threads, fd captures each child's stdout/stderr into an OutputBuffer (src/exec/command.rs) and flushes it under a stdout/stderr lock when the result completes. This guarantees output blocks for each result are atomic.
  • With -x on a single thread, fd inherits stdio so children can be interactive.
  • -0/--print0 makes the per-result output blocks \0-separated. As of 10.4.0 this also affects the boundary between separate -x/-X invocations within a single result. See CHANGELOG.md 10.4.0 entry.

Exit codes

The exit code of the entire fd run with exec is the OR-merge (merge_exitcodes in src/exit_codes.rs) of:

  • The walker's exit code (typically Success).
  • The exit codes of every spawned child.

Any non-zero child status produces ExitCode::GeneralError (1). This is intentional: scripts can rely on fd ... -X cmd exiting non-zero if any subcommand failed.

Common recipes

# Unzip every .zip in parallel:
fd -e zip -x unzip

# Format every C++ file in place:
fd -e cpp -e h -e hpp -x clang-format -i

# Open every test file in vim (single instance):
fd -g 'test_*.py' -X vim

# Show details (long listing) for everything:
fd -l                # equivalent to: fd -X ls -lhd --color=auto

# Combine with ripgrep:
fd -e cpp -e h -X rg 'std::cout'

# md5sum every file, parallelised:
fd -tf -x md5sum

# Convert .jpg -> .png keeping the directory structure:
fd -e jpg -x convert {} {.}.png

# Delete every .DS_Store under the cwd:
fd -H '^\.DS_Store$' -tf -X rm

Caveats

  • Aliases and shell functions cannot be invoked directly. fd ... -x my_alias runs the literal program my_alias. To use a shell function you need fd ... -x bash -c 'my_func "$1"' bash.
  • Placeholders may need quoting to keep your shell from interpreting them. '{}' and '{/.}' are safe in any shell.
  • -x rm -r and race conditions: when removing nested matching directories, the parent may be deleted before the recursive rm reaches the child. The README calls this out as harmless, but be aware.
  • Order is not guaranteed. The walker is parallel and the batches are not sorted. Sort the input with another tool if you need a deterministic order.

Entry points for modification

  • Add a new placeholder. See systems/format-templates — that's where the engine lives.
  • Change parallelism for -x. Edit WorkerState::receive in src/walk.rs. The number of exec workers comes from config.threads.
  • Change the synthesised -l command. Edit determine_ls_command in src/main.rs.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Command execution templates – fd wiki | Factory