just: Shebang Recipes for Python and Bash
A just recipe whose first line is a shebang runs the entire body as one script in that interpreter.
Normally each recipe line runs in its own shell. A shebang recipe runs the whole body together, so you can write multi-line Python or a real Bash script with shared state.
What it does
When a recipe body starts with a "#!" shebang, just writes the body to a temporary file and executes it with that interpreter instead of running each line separately. This means variables, loops, and exit handling persist across lines, unlike the default line-by-line execution.
Common usage
# justfile
# a Python recipe
report:
#!/usr/bin/env python3
import json, sys
data = {"ok": True}
print(json.dumps(data))
# a bash recipe with shared state and strict mode
provision:
#!/usr/bin/env bash
set -euo pipefail
for svc in web worker; do
echo "starting $svc"
doneSyntax
| Form | What it does |
|---|---|
| #!/usr/bin/env bash | Run the body as one bash script |
| #!/usr/bin/env python3 | Run the body as one python script |
| set -euo pipefail | Common strict-mode header inside a bash shebang recipe |
| (default, no shebang) | Each line runs in its own shell process |
In CI
Use a shebang recipe whenever a step needs loops, conditionals, or a non-shell language; it keeps the logic in the repo and out of inline YAML. Add set -euo pipefail in bash shebang recipes so a mid-script failure aborts the job rather than silently continuing.
Common errors in CI
"error: Recipe report with shebang #!/usr/bin/env python3 execution error: No such file or directory" means the interpreter is not installed on the runner. On minimal images /usr/bin/env or bash may be missing, producing "No such file or directory". Without set -e a failing command mid-recipe does not fail the recipe, so the job can pass when it should not.