Skip to main content
Version: Next

Virtual-Sh Runtime

The virtual-sh runtime uses Invowk™'s built-in POSIX-compatible shell interpreter (powered by mvdan/sh). It provides consistent shell behavior across all platforms without requiring an external shell.

How It Works

When you run a command with the virtual-sh runtime, Invowk:

  1. Parses the script using the built-in shell parser
  2. Executes it in an embedded POSIX-like environment
  3. Provides core utilities (echo, test, etc.) built-in

Basic Usage

{
name: "build"
implementations: [{
script: {content: """
echo "Building..."
go build -o bin/app ./...
echo "Done!"
"""}
runtimes: [{name: "virtual-sh", allowed_binaries: ["go"]}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}
invowk cmd build --ivk-runtime virtual-sh

Cross-Platform Consistency

The virtual-sh runtime behaves identically on Linux, macOS, and Windows:

{
name: "setup"
implementations: [{
script: {content: """
# This works the same everywhere!
if [ -d "node_modules" ]; then
echo "Dependencies already installed"
else
echo "Installing dependencies..."
npm install
fi
"""}
runtimes: [{name: "virtual-sh", allowed_binaries: ["npm"]}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}

No more "works on my machine" for shell scripts!

Built-in Utilities

The virtual-sh shell includes core POSIX utilities:

UtilityDescription
echoPrint text
printfFormatted output
test / [Conditionals
true / falseExit with 0/1
pwdPrint working directory
cdChange directory
readRead input
exportSet environment variables

Extended Utilities (u-root)

When enabled in config (default: true), 28 additional POSIX-compliant utilities from the u-root library are available. These include 12 upstream u-root wrappers and 16 custom implementations.

// In your config file
virtual: {
utilities: {
enabled: true
}
}

File Operations (14 utilities)

UtilityDescriptionCommon Flags
base64Encode/decode base64-d (decode)
catConcatenate and display files-u (ignored, compatibility)
cpCopy files and directories-r (recursive), -f (force)
findSearch for files in a directory hierarchy-name (pattern), -type (f, d, l)
gzipCompress or expand files-d (decompress), -c (stdout), -f (force)
lnCreate hard or symbolic links-s (symbolic), -f (force)
lsList directory contents-l (long), -a (all), -R (recursive)
mkdirCreate directories-p (create parents)
mktempCreate temporary files or directories-d (directory), -p (prefix dir)
mvMove or rename files-f (force), -n (no clobber)
realpathResolve absolute path names(none)
rmRemove files and directories-r (recursive), -f (force)
tarArchive files-c (create), -x (extract), -f (file)
touchCreate or update file timestamps-c (no create)

Text Processing (10 utilities)

UtilityDescriptionCommon Flags
basenameStrip directory and suffix from filenames(none)
cutSelect portions of lines-d (delimiter), -f (fields)
dirnameStrip last component from filenames(none)
grepSearch for patterns-i (ignore case), -v (invert), -n (line numbers)
headOutput first N lines-n <num> (default 10)
sortSort lines-r (reverse), -n (numeric), -u (unique)
tailOutput last N lines-n <num> (default 10)
trTranslate charactersSET1 SET2 (character mapping)
uniqFilter adjacent duplicate lines-c (count), -d (duplicates only)
wcCount lines, words, bytes-l (lines), -w (words), -c (bytes)

Other Utilities (4 utilities)

UtilityDescriptionCommon Flags
seqGenerate number sequences-s (separator), -w (equal width)
shasumCompute SHA message digests-a (algorithm: 1, 256, 512)
sleepDelay for a specified time(none — takes duration argument)
teeDuplicate standard input to files-a (append)
note

These utilities implement POSIX behavior. GNU-specific flags (like --color, --time-style) are silently ignored for compatibility.

Error Identification

Errors from u-root utilities are prefixed with [uroot] for easy identification:

[uroot] cp: /source/file: no such file or directory
[uroot] rm: /protected: permission denied

No Silent Fallback

If a u-root command fails, it returns an error immediately. The system does not fall back to host binaries, ensuring bugs are caught rather than masked.

POSIX Shell Features

The virtual-sh shell supports standard POSIX constructs:

Variables

script: {content: """
NAME="World"
echo "Hello, $NAME!"

# Parameter expansion
echo "${NAME:-default}"
echo "${#NAME}" # Length
"""}

Conditionals

script: {content: """
if [ "$ENV" = "production" ]; then
echo "Production mode"
elif [ "$ENV" = "staging" ]; then
echo "Staging mode"
else
echo "Development mode"
fi
"""}

Loops

script: {content: """
# For loop
for file in *.go; do
echo "Processing $file"
done

# While loop
count=0
while [ $count -lt 5 ]; do
echo "Count: $count"
count=$((count + 1))
done
"""}

Functions

script: {content: """
greet() {
echo "Hello, $1!"
}

greet "World"
greet "Invowk"
"""}

Subshells and Command Substitution

script: {content: """
# Command substitution with built-ins
here=$(pwd)
echo "Current directory is $here"

# Subshell
(cd /tmp && echo "In temp: $(pwd)")
echo "Still in: $(pwd)"
"""}

Calling External Commands

The virtual-sh runtime can call host commands only when an implementation explicitly opts in with allowed_binaries:

{
name: "status"
implementations: [{
script: {content: """
# Calls explicitly allowed host binaries
go version
git status
"""}
runtimes: [{
name: "virtual-sh"
allowed_binaries: ["go", "git"]
binary_lookup_mode: "host"
}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}

By default, host binary execution is denied. Named entries such as go and git are resolved according to binary_lookup_mode; allowed_binaries: ["*"] is the explicit opt-out that allows every host binary.

:::caution Not a Sandbox The virtual-sh runtime is not a security sandbox. It is a portable shell interpreter augmented with built-in utilities and host-binary gates. Any allowed host binary (for example git, curl, python, or node) still executes as a native process with host access. By default, the host's environment variables are also inherited (env_inherit_mode: "all").

If you need execution isolation, use the container runtime instead. :::

Environment Variables

Environment variables work the same as in native:

{
name: "build"
env: {
vars: {
BUILD_MODE: "release"
}
}
implementations: [{
script: {content: """
echo "Building in $BUILD_MODE mode"
go build -ldflags="-s -w" ./...
"""}
runtimes: [{name: "virtual-sh", allowed_binaries: ["go"]}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}

Flags and Arguments

Access flags and arguments the same way:

{
name: "greet"
args: [{name: "name", description: "Name to greet", default_value: "World"}]
implementations: [{
script: {content: """
# Using environment variable
echo "Hello, $INVOWK_ARG_NAME!"

# Or positional parameter
echo "Hello, $1!"
"""}
runtimes: [{name: "virtual-sh"}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}

Limitations

No Non-Shell Interpreter Support

The virtual-sh runtime cannot use non-shell script.interpreter values:

// This will NOT work with virtual-sh runtime!
// Runtime validation rejects non-shell script interpreters because
// virtual-sh always uses the embedded mvdan/sh interpreter.
{
name: "bad-example"
implementations: [{
script: {
content: """
#!/usr/bin/env python3
print("This won't work!")
"""
interpreter: "python3"
}
runtimes: [{name: "virtual-sh"}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}

For Python, Ruby, or other interpreters, use the native or container runtime.

Bash-Specific Features

Some bash-specific features are not available:

// These won't work in virtual-sh runtime:
script: {content: """
# Bash arrays (use $@ instead)
declare -a arr=(1 2 3) # Not supported

# Bash-specific parameter expansion
${var^^} # Uppercase - not supported
${var,,} # Lowercase - not supported

# Process substitution
diff <(cmd1) <(cmd2) # Not supported
"""}

Stick to POSIX-compatible constructs for virtual-sh runtime.

Dependency Validation

Root, command, and implementation dependencies are still validated on the host before virtual-sh runs. When a virtual-sh command launches host tools, list those tools in both depends_on.tools and allowed_binaries:

{
name: "build"
depends_on: {
tools: [
// Host dependencies are checked before virtual-sh runs.
{alternatives: ["go"]},
{alternatives: ["git"]}
]
}
implementations: [{
script: {content: "go build ./..."}
runtimes: [{name: "virtual-sh", allowed_binaries: ["go", "git"]}]
platforms: [{name: "linux"}, {name: "macos"}, {name: "windows"}]
}]
}

Advantages

  • Consistency: Same behavior on Linux, macOS, and Windows
  • No shell dependency: Works even if system shell is unavailable (the interpreter is built in, but external commands still require explicit host-binary permission)
  • Portability: Scripts work across all platforms
  • Built-in utilities: Core utilities always available
  • Faster startup: No shell process to spawn

When to Use Virtual-Sh

  • Cross-platform scripts: When the same script must work everywhere
  • CI/CD pipelines: Consistent behavior across build agents
  • Simple shell scripts: When you don't need bash-specific features
  • Embedded environments: When external shells aren't available

Configuration

Configure shared virtual runtime utilities in your Invowk config file:

// ~/.config/invowk/config.cue (Linux)
// ~/Library/Application Support/invowk/config.cue (macOS)
// %APPDATA%invowkconfig.cue (Windows)

virtual: {
utilities: {
// Enable additional utilities from u-root
enabled: true
}
}

Next Steps