The Oak Virtual Shell (libshell) provides a lightweight shell environment that can run in any environment, including those without a native shell (like WASM, embedded systems, or sandboxed environments). It features full integration with the Virtual File System (VFS), making it perfect for portable applications and packed binaries.
- Cross-Platform: Works anywhere Oak runs, including WASM
- VFS Integration: Full support for virtual filesystem operations
- Batch Mode: Execute shell scripts programmatically
- Interactive REPL: Command-line shell interface
- Environment Variables: Export and manage environment state
- Path Navigation: Comprehensive path handling with
..,./, absolute, and relative paths
shell := import('shell')
// Create shell with system filesystem
sh := shell.createShell()
// Create shell with virtual filesystem
Virtual := import('Virtual')
vfs := Virtual.createVirtualFS({ ... })
sh := shell.createShell(vfs)Execute a single command and return its exit code.
exitCode := sh.exec('ls /home')
exitCode := sh.exec('cat file.txt')
if sh.exec('cd /nonexistent') {
0 -> println('Success')
_ -> println('Failed')
}Returns:
0on success- Non-zero error code on failure
:exitif the exit command was executed
Start an interactive read-eval-print loop (REPL).
sh.repl()This will show an interactive prompt:
Oak Shell v0.2 (type "help" for commands, "exit" to quit)
/> ls
/> cd /home
/home> pwd
/home
/home> exit
Execute multiple commands from a script string.
script := '
# Setup project
cd /app
mkdir src
touch src/main.oak
ls src
'
exitCode := sh.batch(script)Returns the exit code of the last command, or 0 if the script completes successfully.
Get the current working directory.
cwd := sh.getCurrentDir()
println(cwd) // "/home/user"Get a copy of the environment variables.
env := sh.getEnv()
println(env.PATH)
println(env.HOME)Set an environment variable programmatically.
sh.setEnv('DEBUG', 'true')
sh.setEnv('APP_NAME', 'MyApp')Print working directory.
pwd
# /home/userChange directory. Supports absolute paths, relative paths, ./, and ../.
cd /home
cd user
cd ./documents
cd ../..
cd # goes to $HOMEList files in the current or specified directory.
ls
ls /home
ls ..Print contents of one or more files.
cat file.txt
cat file1.txt file2.txt file3.txtCreate empty files.
touch newfile.txt
touch file1.txt file2.txtCopy a file.
cp original.txt copy.txt
cp /home/file.txt /backup/file.txtMove or rename a file.
mv old.txt new.txt
mv /tmp/file.txt /home/file.txtRemove files. Use -r flag for recursive deletion (future support).
rm file.txt
rm file1.txt file2.txt
rm -r directoryCreate directories.
mkdir newdir
mkdir dir1 dir2 dir3
mkdir /home/user/projectsPrint text to stdout.
echo Hello World
echo File created successfullyDisplay all environment variables.
env
# PATH=/bin:/usr/bin
# HOME=/home
# PWD=/Set environment variables.
export DEBUG=true
export APP_NAME=MyApplication
export PATH=/usr/local/bin:/usr/binDisplay help information about available commands.
helpExit the shell.
exitThe shell can be invoked from the command line using the oak shell command:
Start an interactive shell session:
oak shellExecute a single command with -c:
oak shell -c "ls /home"
oak shell -c "cat config.json"Execute commands from a file:
oak shell setup.shExample script file (setup.sh):
#!/usr/bin/env oak shell
# Project setup script
echo Setting up project...
cd /app
mkdir src
mkdir config
touch src/main.oak
touch config/settings.json
echo Done!Virtual := import('Virtual')
shell := import('shell')
vfs := Virtual.createVirtualFS({
'/home/README.md': '# Project\nWelcome!'
'/home/config.json': '{"version": "1.0"}'
})
sh := shell.createShell(vfs)
sh.exec('cd /home')
sh.exec('ls')
sh.exec('cat README.md')
sh.exec('cp config.json backup.json')
sh.exec('ls')shell := import('shell')
sh := shell.createShell()
setupScript := '
# Initialize project structure
echo Creating project structure...
mkdir /app
cd /app
mkdir src lib test
touch src/main.oak
touch README.md
echo export PROJECT_NAME=MyApp >> .env
ls
echo Project initialized!
'
result := sh.batch(setupScript)
if result = 0 {
println('Setup completed successfully')
}shell := import('shell')
Virtual := import('Virtual')
vfs := Virtual.createVirtualFS({
'/data/input.txt': 'raw data'
})
sh := shell.createShell(vfs)
sh.exec('cd /data')
sh.exec('cat input.txt')
sh.exec('cp input.txt processed.txt')
sh.exec('cat processed.txt')shell := import('shell')
sh := shell.createShell()
// Configure environment
sh.exec('export APP_ENV=production')
sh.exec('export LOG_LEVEL=info')
sh.exec('export MAX_WORKERS=4')
// Verify configuration
sh.exec('env')
// Access from Oak code
env := sh.getEnv()
println('Running in: ' << env.APP_ENV)The shell supports comprehensive path resolution:
- Absolute paths:
/home/user/file.txt - Relative paths:
documents/file.txt - Current directory:
./file.txt - Parent directory:
../file.txt - Multiple parent levels:
../../other/file.txt - Home directory:
~(viacdwith no args)
Examples:
# Starting from /home/user
cd documents # /home/user/documents
cd ./api # /home/user/documents/api
cd .. # /home/user/documents
cd ../../tmp # /tmp
cd / # /When using oak pack with embedded VFS, the shell automatically uses the virtual filesystem:
# Create packed binary with VFS
oak pack --entry main.oak --output myapp --include "config:./config,data:./data"In your main.oak:
shell := import('shell')
// Automatically uses packed VFS if available
sh := shell.createShell(___packed_vfs())
sh.repl() // Interactive shell with access to embedded filesCommands return exit codes following Unix conventions:
0: Success1: General error127: Command not found:exit: Special value indicating shell exit requested
result := sh.exec('cat nonexistent.txt')
if result {
0 -> println('Success')
1 -> println('File not found or read error')
127 -> println('Unknown command')
:exit -> println('Exit requested')
}Current limitations:
- Limited real directory support: On real filesystems, directory creation may be limited
- No job control: Background jobs with
&are not supported - No command substitution:
$(command)or backticks are not supported
Implemented features:
- Add pipe support (
|) with full stdout capture between stages - Add redirection support (
>,>>,<) with captured output - Add wildcard/glob expansion (
*,?) - Add quoted argument parsing (single and double quotes)
- Add command history (
historycommand,getHistory()API) - Add alias support (
alias,unaliascommands,addAlias()/removeAlias()API) - Add more commands (
grep,find,head,tail,wc,sort,uniq,tee) - Add tab completion engine (
tabcommand,complete()API) - Full stdout capture for pipe stages and redirections
Future enhancements:
- Add readline-like line editing (arrow keys, Ctrl-A/E)
- Add job control (
&) - Add command substitution (
$(command))
Search for a text pattern in files or piped stdin.
grep TODO *.oak
cat file.txt | grep error
grep main src/app.oak src/lib.oakPrint the first N lines (default 10) of a file or stdin.
head file.txt
head -5 file.txt
cat log.txt | head -20Print the last N lines (default 10) of a file or stdin.
tail file.txt
tail -5 file.txt
cat log.txt | tail -20Count lines, words, and characters.
wc file.txt
cat file.txt | wcRecursively find files, optionally matching a glob pattern.
find .
find /home -name *.oak
find src -name test?.txtSort lines alphabetically. Use -r for reverse order.
sort names.txt
cat data.txt | sort -rRemove adjacent duplicate lines.
sort names.txt | uniq
uniq sorted.txtCopy stdin to a file and stdout. Use -a to append.
cat data.txt | tee backup.txt
echo hello | tee -a log.txtDefine or show command aliases.
alias ll='ls -l'
alias # show all aliasesRemove a command alias.
unalias llShow command history for the current session.
historyShow completions for a partial command or path.
tab gr # shows: grep
tab so # shows: sort
tab src/ # shows files in src/ directoryThe shell provides a completion engine accessible in two ways:
Use the tab command interactively:
/> tab ec
echo
/> tab /ho
/homeCall complete(partial) from Oak code for GUI or custom shell integrations:
shell := import('shell')
sh := shell.Shell()
sh.exec('mkdir src')
sh.exec('touch src/main.oak')
sh.exec('touch src/lib.oak')
matches := sh.complete('cat src/')
// => ['src/main.oak', 'src/lib.oak']
cmdMatches := sh.complete('gr')
// => ['grep']Completion behavior:
- At command position (first word): completes command names and aliases
- At argument position (after first word): completes file/directory paths
- After trailing space: lists all files in current directory
Commands can be chained with | to pass data between stages:
cat file.txt | grep error | head -5
find . -name *.oak | sortOutput can be redirected to files with > (overwrite) or >> (append).
Input can be read from files with <.
ls > files.txt
echo hello >> log.txt
grep error < app.logArguments with spaces can be quoted using single or double quotes:
echo "hello world"
echo 'single quoted'
echo "escaped \"quote\""Wildcard patterns are expanded against the filesystem:
ls *.oak # all .oak files
cat test?.txt # test1.txt, testA.txt, etc.Run the shell tests:
oak test/shell.test.oakOr run all tests including shell tests:
oak test/main.oak