Skip to content

Shell scripts

Index

shell_scripts::has_shell_shebang

Return true if the given file's first line contains a bash, sh, or bats shebang. bats is included because shfmt and shellcheck both auto-detect it from the shebang and apply their bats-aware dialect.

Arguments

  • $1 (file): path

Exit codes

  • 0: if true
  • 1: if false

shell_scripts::is_shell_file

Return true if the given path is a shell file: either it carries a shell extension (.sh/.bash/.bats) or its first line is a shell shebang. Mirrors how shfmt --find classifies files when scanning a directory, so an explicitly passed file is judged by the same rule a discovered one is. .bats is matched by extension because those files open with @test, not a shebang.

Arguments

  • $1 (file): path

Exit codes

  • 0: if true
  • 1: if false

shell_scripts::assert_paths_exist

Die if any of the given paths does not exist on disk.

Arguments

  • ... (paths): to validate

shell_scripts::find

Emit shell-script paths under the repo root when called with no args; excludes /other/ (third-party copies), /.shdoc/ (vendored shdoc submodule), and /.direnv/ (Nix/direnv-managed cache). With args: each arg is either a directory (recursed via shfmt --find) or a file (emitted as-is). Caller must validate that each arg exists before calling. Output: stdout — shell script file paths, one per line

Arguments

  • ... (files): or directories to search (optional; defaults to all scripts under the repo root)

Exit codes

  • 0: at least one shell file was emitted
  • 1: the no-args scan matched nothing

shell_scripts::filter

Filter candidate paths down to processable shell scripts: - Files that are not shell files are warned and skipped. A shell file is one carrying a .sh/.bash/.bats extension or a shell shebang (shell_scripts::is_shell_file), the same predicate the shfmt step in check-scripts uses. Gating on the shebang alone would leave 31 of 83 test files silently unlinted. - Files under /other/ require interactive confirmation (prompt::ny). Output goes into a caller-provided array via nameref so prompts can run in the caller's shell (avoids capturing prompt output in process subs).

$2..$N = candidate paths

Arguments

  • $1 (name): of output array (will be cleared and populated)

shell_scripts::find_root_only

Emit shell-script paths that live directly in the repo root (no subdirectories). Used by .ci/build-docs and .ci/check-shdoc-headers to enumerate project-root executables such as shellcheck-scripts, check-scripts, run-install-scripts, run-set-up-scripts, run-tests. Filters to files whose first line contains a bash/sh/bats shebang (via shell_scripts::has_shell_shebang). Dotfiles are excluded wholesale — the * glob does not match them — so .envrc and a root-level .functions.bash loader never become candidates in the first place.

Function has no arguments.

Exit codes

  • 0: at least one root-level shell script was emitted
  • 1: the repo root holds no shell scripts

Output on stdout

  • file paths, one per line

shell_scripts::requires_help_flag

Return true if a top-level script is required to call args::handle_help_flag. The call is mandatory on every top-level script with two exemptions, and both are structural rather than listed: a pass-through script carries the mandatory pass-through comment, and a standalone script never sources the function library, so the helper does not exist for it to call. The library test matches functions.bash rather than any source line — a misc/ script sourcing ~/.profile is still standalone, while a Docker script sourcing ${DOCKER_COMPOSE_DIR}/functions.bash gets this repo's helpers transitively and is not exempt.

Deliberate looseness: pass-through marks two different exemptions — the help-flag one this predicate wants, and the arg-count-guard one, which a merely variadic script also carries. Three scripts (mvn-fresh, claude-resume, sync-flatpaks) carry the arity marker while still owning their own --help, so this reads them as exempt when they are not. That costs nothing today because all three call the helper anyway, and erring toward exempt keeps the predicate free of false positives. Tightening it needs a marker that distinguishes the two exemptions, which is a convention change rather than a lint change.

Arguments

  • $1 (file): path to script

Exit codes

  • 0: the script must call args::handle_help_flag
  • 1: the script is exempt as a pass-through or a standalone