No description
  • Rust 97.4%
  • Nix 2.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-12 12:46:27 +03:00
crates comma: resolve installed and symlinked commands 2026-09-12 12:46:27 +03:00
#AGENTS.md# add AGENTS.md 2026-09-12 11:20:19 +03:00
.gitignore scaffold Rust workspace and CLI input handling 2026-09-11 11:33:35 +00:00
AGENTS.md add AGENTS.md 2026-09-12 11:20:19 +03:00
Cargo.lock comma: add interactive command launcher 2026-09-12 12:46:27 +03:00
Cargo.toml comma: add interactive command launcher 2026-09-12 12:46:27 +03:00
flake.lock nix: use packaged nix-index database 2026-09-12 12:46:27 +03:00
flake.nix comma: resolve installed and symlinked commands 2026-09-12 12:46:27 +03:00
README.md pass on unresolver executables 2026-09-11 22:31:37 +03:00
SPEC.md initial commit. add SPEC.md 2026-09-11 14:25:10 +03:00

bash-with-nix

Apart from this sentence, this repo is slop-coded, and hasnt 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:

  1. confirm nix-locate is in PATH (nix-locate --version);
  2. run nix-index as the same user running bash-with-nix;
  3. check that the database is readable and retry with --verbose;
  4. 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.