|
shell-cmd v1.2
Recursively find files matching a regex and execute a shell command for each match.
|
Recursively find files matching a regex pattern and execute a shell command for each match.
Requires C++20 (GCC 13+, Clang 16+).
Or install system-wide:
| Placeholder | Description |
|---|---|
| %0 | Filename only (no path) |
| %1 | Full path to matched file |
| %2+ | Extra arguments from command line |
| b | Basename without extension (e.g., report from report.txt) |
| e | File extension including dot (e.g., .txt) |
| Short | Long | Description |
|---|---|---|
| -z | --regex-match | Use regex_match (entire path must match) instead of the default regex_search (substring match) |
| -b | --glob | Treat the search pattern as a glob (*, ?, [...]) instead of regex |
| -i | --glob-exclude | Treat the exclude pattern (-x) as a glob instead of regex |
| -f EXPR | --expr EXPR | Expression filter — compose glob(), regex(), regex_match() with and/or/not (replaces the regex positional argument) |
| -n | --dry-run | Dry-run — print commands without executing |
| -v | --verbose | Verbose — print each command before running |
| -a | --all | Include hidden files and directories |
| -l | --list-all | Run one command with %0 set to a space-delimited list of all matched paths |
| -d N | --depth N | Max recursion depth (0 = current directory only) |
| -s SIZE | --size SIZE | Filter by size: +10M (>10 MB), -1K (<1 KB), `4096` (exact). Suffixes: K, M, G |
| -m DAYS | --mtime DAYS | Filter by modification time: +7 (older than 7 days), -1 (within last day) |
| -p MODE | --perm MODE | Filter by permissions (octal), e.g. 755 |
| -u USER | --user USER | Filter by owner username |
| -g GROUP | --group GROUP | Filter by group name |
| -t TYPE | --type TYPE | Filter by type: f (file), d (directory), l (symlink) |
| -x REGEX | --exclude REGEX | Exclude files/directories matching REGEX (or a glob when combined with -i) |
| -e | --stop-on-error | Stop on first command failure |
| -c | --confirm | Prompt for confirmation before each command |
| -j N | --jobs N | Run N commands in parallel (default: 1) |
| -w SHELL | --shell SHELL | Shell to use for execution (default: /bin/bash) |
| -h | --help | Show help |
shell-cmd supports three independent switches that control how the search pattern and exclude pattern are interpreted. They can be combined freely.
By default, the third positional argument is an ECMAScript regex tested as a substring search against each file's full path. If the pattern appears anywhere in the path, the file matches.
Because this is a substring search, you do not need .* at the start of the pattern — \.cpp$ is enough to match all paths ending in .cpp.
With -z, the regex must match the entire path (equivalent to wrapping the pattern in ^...$). This is useful when you want precise control:
With -b, write familiar shell wildcard patterns instead of regex. Glob metacharacters:
| Glob | Meaning | Regex equivalent |
|---|---|---|
| * | Match any number of characters | .* |
| ? | Match exactly one character | . |
| [abc] | Match one of the listed characters | [abc] |
| [!abc] or [^abc] | Match any character not listed | [^abc] |
All other regex-special characters (., +, |, (, ), etc.) are automatically escaped, so you never need backslashes.
The glob pattern is anchored — it must match the entire path (internally converted to ^...$).
When both -b and -z are active, the glob is converted to regex and then full-path matching is applied. This is useful for matching the complete path with glob syntax:
The -x / --exclude option skips files and directories whose filename (not full path) matches the given pattern. By default, the exclude pattern is a regex (substring search):
Add -i to treat the -x pattern as a glob instead of regex. The glob is converted to an anchored regex internally, so it must match the entire filename:
The -x pattern and the search pattern are independent — you can use --glob for the search pattern while keeping -x as a regex (the default), or vice versa:
The -f / --expr option lets you compose complex match logic in a single argument, combining glob(), regex(), and regex_match() with boolean operators. When --expr is used, the third positional argument (regex) is not required — the expression replaces it.
Expressions are built from functions, boolean operators, and parentheses:
| Element | Description |
|---|---|
| glob("pattern") | Convert the glob to an anchored regex and apply regex_search (same as --glob) |
| regex("pattern") | Substring regex search (same as default mode) |
| regex_search("pattern") | Alias for regex() |
| regex_match("pattern") | Full-path regex match (same as --regex-match) |
| and | Both sides must match |
| or | Either side must match |
| not | Negate the following expression |
| ( … ) | Group sub-expressions to control precedence |
Operator precedence (highest to lowest): not, and, or. Use parentheses to override.
Match C++ files, exclude build directories:
Single function — equivalent to a regex positional argument:
Nested boolean logic — Python or Rust sources, excluding tests and vendor:
Full-path matching inside an expression:
Combine --expr with other options (-x, --size, --type):
Count lines in all .cpp files:
Dry-run to preview what would be executed:
Copy matched files to a destination, using filename-only placeholder:
Limit search to current directory (no recursion):
Include hidden files:
Use extra arguments — %2 is replaced with the value passed after the regex:
Multiple extra arguments:
Convert WAV to MP3, using b for the output filename without extension:
Organize files by extension:
Run a single batch command with all matches:
In this mode, %0 is substituted with a single space-separated string containing every matched path.
Find large files (over 10 MB):
Delete files older than 30 days, with dry-run:
Find executable files (permission 755):
List files owned by root:
List only directories matching a pattern:
Combine filters — large .log files modified recently:
Match all C/C++ source and header files:
Format C/C++ files, excluding build directories:
Format C/C++ files, excluding with a glob exclude pattern:
Match files with single-character extensions:
Run commands in parallel with 4 jobs:
Confirm before each destructive command:
Stop on first error:
The program recursively walks the specified directory using std::filesystem. For each entry:
When using --glob, the search pattern and/or exclude pattern (with -i) are converted to anchored regex (^...$) with proper escaping before matching begins. When using --regex-match, the search regex is wrapped in ^(?:...)$ for full-path matching.
When using -l / --list-all, shell-cmd does not run a command per file; it collects all matched paths, joins them with spaces, and runs the command exactly once with %0 replaced by the full list string.
| Feature | shell-cmd | find -exec |
|---|---|---|
| Filename placeholder | %0 gives the filename without the path | No equivalent — requires sh -c + basename |
| Full path placeholder | %1 | {} |
| Extra arguments | %2, %3, … with validation | Not supported — use shell variables |
| Pattern matching | ECMAScript regex (substring or full-path), glob mode (-b), or composable expressions (--expr) | Glob (-name) or implementation-varying -regex |
| Exclude patterns | Built-in -x with regex or glob (-i) | Requires negation logic or ! -name |
| Expression filters | Built-in --expr — combine glob(), regex(), regex_match() with and/or/not | Boolean -and/-or/-not between find predicates |
| Dry-run | Built-in -n flag | No native support |
| Verbose mode | Built-in -v flag | No native support |
| Filter by metadata | Size (-s), time (-m), permissions (-p), owner (-u), group (-g), type (-t) | Size, time, permissions, ownership, type, boolean logic |
| Parallel execution | Built-in -j N | Requires xargs -P or GNU parallel |
| Confirm mode | Built-in -c flag | Requires -ok (not universally supported) |
| Stop on error | Built-in -e flag | No native support |
| Summary stats | Automatic (matched/run/failed counts) | No native support |
| Portability | Requires C++20 build | POSIX-standard, available everywhere |
Side-by-side example — copy all .txt files to a backup directory, preserving filenames:
In short, shell-cmd offers a more ergonomic command-templating experience with built-in dry-run, parallel execution, confirm mode, stop-on-error, exclude patterns (regex or glob), composable expression filters (--expr with and/or/not), and summary statistics.
See [LICENSE](LICENSE).