Interpreters
By default, Invowk™ executes scripts using a shell. But you can use other interpreters like Python, Ruby, Node.js, or any executable that can run scripts.
Auto-Detection from Shebang
When a script starts with a shebang (#!), Invowk automatically uses that interpreter:
{
name: "analyze"
implementations: [{
script: {content: """
#!/usr/bin/env python3
import sys
import json
data = {"status": "ok", "python": sys.version}
print(json.dumps(data, indent=2))
"""}
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}
Common shebang patterns:
| Shebang | Interpreter |
|---|---|
#!/usr/bin/env python3 | Python 3 (portable) |
#!/usr/bin/env node | Node.js |
#!/usr/bin/env ruby | Ruby |
#!/usr/bin/env perl | Perl |
#!/bin/bash | Bash (direct path) |
Explicit Interpreter
Specify an interpreter directly on the script object:
{
name: "script"
implementations: [{
script: {
content: """
import sys
print(f"Python {sys.version_info.major}.{sys.version_info.minor}")
"""
interpreter: "python3" // Explicit
}
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}
The explicit interpreter takes precedence over shebang detection. If resolved script content also has a different shebang interpreter or argument list, Invowk keeps script.interpreter authoritative and reports an advisory warning during invowk validate and --ivk-dry-run.
interpreter: "auto" behaves like an omitted interpreter: Invowk uses the shebang when present and falls back to runtime shell behavior otherwise.
Interpreter with Arguments
Pass arguments to the interpreter:
{
name: "unbuffered"
implementations: [{
script: {
content: """
import time
for i in range(5):
print(f"Count: {i}")
time.sleep(1)
"""
interpreter: "python3 -u" // Unbuffered output
}
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}
More examples:
// Perl with warnings
script: {content: "...", interpreter: "perl -w"}
// Ruby with debug mode
script: {content: "...", interpreter: "ruby -d"}
// Node with specific options
script: {content: "...", interpreter: "node --max-old-space-size=4096"}
Container Interpreters
Interpreters work in containers too:
{
name: "analyze"
implementations: [{
script: {content: """
#!/usr/bin/env python3
import os
print(f"Running in container at {os.getcwd()}")
"""}
runtimes: [{
name: "container"
image: "python:3-slim"
}]
platforms: [{name: "linux"}]
}]
}
Or with explicit interpreter:
{
name: "script"
implementations: [{
script: {
content: """
console.log('Hello from Node in container!')
console.log('Node version:', process.version)
"""
interpreter: "node"
}
runtimes: [{
name: "container"
image: "node:22-slim"
}]
platforms: [{name: "linux"}]
}]
}
Accessing Arguments
Arguments work the same with any interpreter:
{
name: "greet"
args: [{name: "name", description: "Name to greet", default_value: "World"}]
implementations: [{
script: {content: """
#!/usr/bin/env python3
import sys
import os
# Via command line args
name = sys.argv[1] if len(sys.argv) > 1 else "World"
print(f"Hello, {name}!")
# Or via environment variable
name = os.environ.get("INVOWK_ARG_NAME", "World")
print(f"Hello again, {name}!")
"""}
runtimes: [{name: "native"}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}
Supported Interpreters
Invowk validates interpreter names against an allowlist for security (SC-08). The following interpreters are accepted:
- Shell:
sh,bash,zsh,fish,dash,ksh,mksh - Python:
python3,python,python2 - JavaScript:
node,deno,bun - Ruby:
ruby - Perl:
perl - PHP:
php - Lua:
lua - R:
Rscript - Windows:
pwsh,powershell,cmd
Interpreters not in this list are rejected with an UnsafeInterpreterSpecError. Shell metacharacters (;|& etc.) in interpreter specs are also rejected. When using /usr/bin/env, the actual interpreter (second argument) is validated against the same allowlist.
Virtual-Sh Runtime Limitation
Non-shell script.interpreter values are not supported with the virtual-sh runtime:
// This will NOT work with the virtual-sh runtime.
// Runtime validation error: virtual-sh uses mvdan/sh and
// cannot execute non-shell script interpreters.
{
name: "bad"
implementations: [{
script: {content: "print('hello')", interpreter: "python3"}
runtimes: [{name: "virtual-sh"}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}
The virtual-sh runtime uses the built-in mvdan/sh interpreter and cannot execute Python, Ruby, or other interpreters. Use native, virtual-lua, or container runtime instead. Runtime configs do not accept interpreter; a shape like runtimes: [{name: "native", interpreter: "python3"}] is invalid.
Fallback Behavior
When there is no shebang and no script.interpreter:
- Native runtime: Uses system's default shell
- Container runtime: Uses
/bin/sh -c
--ivk-dry-run prints interpreter provenance so you can see whether execution will use an explicit interpreter, a detected shebang, or default shell behavior.
Best Practices
- Use shebang for portability: Scripts work standalone too
- Use
/usr/bin/env: More portable than direct paths - Explicit interpreter for no-shebang scripts: When you don't want a shebang line
- Match container image: Ensure interpreter exists in the image
Next Steps
- Working Directory - Control execution location
- Platform-Specific - Per-platform implementations