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:
- Parses the script using the built-in shell parser
- Executes it in an embedded POSIX-like environment
- 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:
| Utility | Description |
|---|---|
echo | Print text |
printf | Formatted output |
test / [ | Conditionals |
true / false | Exit with 0/1 |
pwd | Print working directory |
cd | Change directory |
read | Read input |
export | Set 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)
| Utility | Description | Common Flags |
|---|---|---|
base64 | Encode/decode base64 | -d (decode) |
cat | Concatenate and display files | -u (ignored, compatibility) |
cp | Copy files and directories | -r (recursive), -f (force) |
find | Search for files in a directory hierarchy | -name (pattern), -type (f, d, l) |
gzip | Compress or expand files | -d (decompress), -c (stdout), -f (force) |
ln | Create hard or symbolic links | -s (symbolic), -f (force) |
ls | List directory contents | -l (long), -a (all), -R (recursive) |
mkdir | Create directories | -p (create parents) |
mktemp | Create temporary files or directories | -d (directory), -p (prefix dir) |
mv | Move or rename files | -f (force), -n (no clobber) |
realpath | Resolve absolute path names | (none) |
rm | Remove files and directories | -r (recursive), -f (force) |
tar | Archive files | -c (create), -x (extract), -f (file) |
touch | Create or update file timestamps | -c (no create) |
Text Processing (10 utilities)
| Utility | Description | Common Flags |
|---|---|---|
basename | Strip directory and suffix from filenames | (none) |
cut | Select portions of lines | -d (delimiter), -f (fields) |
dirname | Strip last component from filenames | (none) |
grep | Search for patterns | -i (ignore case), -v (invert), -n (line numbers) |
head | Output first N lines | -n <num> (default 10) |
sort | Sort lines | -r (reverse), -n (numeric), -u (unique) |
tail | Output last N lines | -n <num> (default 10) |
tr | Translate characters | SET1 SET2 (character mapping) |
uniq | Filter adjacent duplicate lines | -c (count), -d (duplicates only) |
wc | Count lines, words, bytes | -l (lines), -w (words), -c (bytes) |
Other Utilities (4 utilities)
| Utility | Description | Common Flags |
|---|---|---|
seq | Generate number sequences | -s (separator), -w (equal width) |
shasum | Compute SHA message digests | -a (algorithm: 1, 256, 512) |
sleep | Delay for a specified time | (none — takes duration argument) |
tee | Duplicate standard input to files | -a (append) |
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
- Native Runtime - For full shell access
- Virtual-Lua Runtime - For portable Lua automation
- Container Runtime - For isolated execution