Skip to main content
Version: 0.14.0

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:

ShebangInterpreter
#!/usr/bin/env python3Python 3 (portable)
#!/usr/bin/env nodeNode.js
#!/usr/bin/env rubyRuby
#!/usr/bin/env perlPerl
#!/bin/bashBash (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

  1. Use shebang for portability: Scripts work standalone too
  2. Use /usr/bin/env: More portable than direct paths
  3. Explicit interpreter for no-shebang scripts: When you don't want a shebang line
  4. Match container image: Ensure interpreter exists in the image

Next Steps