- Rust 97.4%
- Nix 2.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| crates | ||
| #AGENTS.md# | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| SPEC.md | ||
bash-with-nix
Apart from this sentence, this repo is slop-coded, and hasn’t been human-verified.
bash-with-nix statically inspects a Bash script, finds literal external commands missing from the caller's PATH, resolves them with a local nix-index database, and runs the unchanged script in one nix shell containing the selected nixpkgs packages. If everything is already available, it runs Bash directly and does not invoke Nix.
Install
From a local checkout, run directly or build the package:
nix run . -- ./script.sh argument
nix build
./result/bin/bash-with-nix --help
To install it into your profile:
nix profile install .
The runtime requires bash, a flakes-enabled Nix (nix-command and flakes), nix-locate, and a local nix-index database. A remote flake reference can be used instead of . when consuming a hosted checkout.
Usage and examples
bash-with-nix ./script.sh arg1 'arg two'
bash-with-nix -c 'jq . "$1"' custom-zero input.json
printf '%s\n' 'fortune | cowsay' | bash-with-nix
bash-with-nix --dry-run --verbose ./script.sh
bash-with-nix --json ./script.sh
bash-with-nix --fail-on-unresolved ./script.sh
-c follows bash -c: the first argument after the command is $0, defaulting to bash-with-nix, and later arguments are $1, $2, and so on. For a file, the remaining arguments become the script's positional parameters. Use -- when a file or argument begins with -.
The wrapper normally emits nothing of its own on success. --verbose explains command discovery, existing PATH hits, package choices and alternatives. --json emits a machine-readable report and implies --dry-run. --refresh-index runs nix-index once before resolving. If an executable cannot be resolved, it is left unchanged and Bash is still run; pass --fail-on-unresolved to refuse execution instead.
Set up nix-index
Install nix-index (which supplies both nix-index and nix-locate) and create its database:
nix profile install nixpkgs#nix-index
nix-index
The initial indexing operation can download metadata and take some time. Run nix-index periodically, or pass --refresh-index when an explicit refresh is wanted. Normal runs never silently refresh the database.
If resolution reports that the index is unavailable:
- confirm
nix-locateis inPATH(nix-locate --version); - run
nix-indexas the same user runningbash-with-nix; - check that the database is readable and retry with
--verbose; - update an old database if a known package or executable is absent.
An executable may genuinely be absent from nixpkgs or may live in an output not represented by the current index. Bash handles unresolved names at runtime by default. With --fail-on-unresolved, the diagnostic lists every statically required name and the script is not started.
Static-analysis limitations
This tool parses Bash; it does not evaluate it. It discovers literal command words, including supported command, exec, and env forms, but intentionally does not infer names produced by variables, substitutions, arithmetic, globbing, concatenation, or eval. Dynamic command positions are reported and left for Bash to resolve at runtime. Literal source/. operands are recursively analyzed (relative paths use the containing file's directory and bare names use the initial PATH); a missing deterministic source prevents execution, while dynamic source operands are reported but cannot be followed. Runtime changes to PATH, aliases, and package-specific runtime configuration are not inferred.
Functions defined in the same script, Bash builtins and keywords, commands containing /, and executables already present in the initial PATH are not resolved. A selected package can expose additional programs and Nix may prepend them to PATH; such a program can therefore shadow a same-named executable from the caller even though that name itself was classified as present.
For piped input, the complete standard input must be consumed before parsing. The captured script is executed from a secure temporary file that is removed afterward, so the script cannot subsequently read the original piped bytes from stdin. Use a script file or -c when the executed script itself needs the caller's stdin.
Development
Use the flake-provided toolchain:
nix develop
cargo fmt --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace
nix flake check
The default flake checks build the package and run the fast Rust test suite. The separate nix-boundary-e2e check runs the packaged binary with a deliberately restricted PATH, protocol-faithful nix-locate/nix shims, and a real executable from nixpkgs. A recursive real nix shell cannot run portably inside a normal Nix build sandbox; real Nix execution is therefore exercised manually outside the sandbox when needed.