A tiny Go CLI that iterates over directories and runs a command for each directory containing a specific subfolder (e.g. .git). Supports depth-limited traversal, parallel execution, verbose logging, and dry runs.
- Depth-limited traversal starting from a root directory (default: current directory)
- Only runs for directories that contain a given subfolder (
--subdir); when empty, every directory matches - Runs any shell command in each matching directory
- Parallel execution (
--parallel N, default = number of CPU cores) - Skip hidden folders (name starts with
.) by default - Verbose matches listing (
-v) and dry-run mode (--dry-run) - Configurable output prefix path: relative to current dir (default) or full path
- Customizable prefix via
--prefix-template(default:[{{.Base}}]) - Prints a final summary:
Commands executed: N
You can provide the command either directly or after a -- separator (both forms are accepted):
foreach [flags] <command> [args...]
foreach [flags] -- <command> [args...]
Flags:
-r, --rootstring: Root directory to start from (default ".")-s, --subdirstring: Subfolder to check for (no default). If empty, all directories match.-l, --levelint: Recursion depth, 1 = only immediate subfolders (default 1)-p, --parallelint: Run up to N commands in parallel (default = number of CPU cores)--skip-hiddenbool: Skip folders whose names start with '.' (default true)-v: Verbose output (prints matches)-n, --dry-run: List matching directories but do not execute the command--prefixstring: Output prefix mode:relative(default) orfull--prefix-templatestring: Custom Go template for prefix (default:[{{.Base}}]). Vars:{{.Display}}{{.Dir}}{{.Rel}}{{.Root}}{{.CWD}}{{.Base}}. Example:--prefix-template "[{{.Base}}] "-e, --sepstring: Separator line printed between command outputs (default disabled; set a non-empty string to enable)
Notes:
- Depth semantics: level=1 visits only immediate subfolders of
--root(the root itself isn’t tested). - Output prefix: by default, the path in brackets is relative to your current working directory (e.g.,
[.\sub\project] ...). Use--prefix fullfor absolute paths (e.g.,[C:\path\to\project] ...). - Exit code is non-zero if any command execution fails in any directory.
- Run
git statusin every immediate subfolder that has a.gitdirectory:
foreach --subdir .git -- git status
- Recursively down 3 levels and run
npm ciwhere anode_modulesfolder exists:
foreach --level 3 --subdir node_modules -- npm ci
- Run two commands in parallel and print matches as they’re found:
foreach --parallel 2 -v -- git fetch --all --prune
- Dry-run to just list matching directories without running anything:
foreach --subdir .git --dry-run -v -- git status
- Custom separator between outputs (disable with empty string):
foreach --sep "----------" -- git status
foreach --sep "" -- git status
- Show absolute paths in the output prefix:
foreach --prefix full -- git status
- Use a custom prefix template (repo folder name only):
foreach --prefix-template "[{{.Base}}] " -- git status
- Windows shell built-ins (use
cmd /c ...):
foreach -- cmd /c echo OK
Install the latest released binary to your GOBIN/GOPATH/bin (recommended):
go install github.com/dmundt/foreach/cmd/foreach@latest
From a local clone, you can also build or install:
go build -o foreach.exe ./cmd/foreach
go install ./cmd/foreach
Then run foreach from anywhere on your PATH.
This repo includes a GitHub Actions workflow that runs on every push and pull request:
- OS matrix:
ubuntu-latest,windows-latest - Go versions:
1.21.x - Steps:
- Build:
go build ./... - Vet:
go vet ./... - Test:
go test ./... - Static analysis:
staticcheck ./...
- Build:
You can adjust the matrix to speed up CI (e.g., limit to the latest Go release) if needed.
MIT License. See LICENSE for details.