We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.
SunnyShell
SunnyShell is a shell-style programming language designed by PSTF.
Core Philosophy
- No text-munging by default.
- Command after arguments.
- Explicit over implicit.
- Immutable by default.
Core Features
The Stack & Data Types
You interact with a single, persistent stack. Typing a literal pushes it onto the stack.
These are all valid literals.
114514
3.1415926
"Hello, World!"
true
false
-135
-123.456
[1 2 "Three"]
{ name:"file" size:1024 }
{ size 1024 gt }
Example:
Admin ~/ !> 42 "answer" # Stack now: [0] 42, [1] "answer"
The Command Language
Built-in Commands (Words)
Every built-in is a lowercase word that pops required arguments from the stack and pushes the result back.
Category Words Effect Stack dup, drop, swap, over, rot... Standard stack manipulation. Math add, sub, mul, div, mod, abs... Pop two, push result. Logic gt, lt, eq, ne, and, or, not... Pop two, push boolean. Lists/Tables length, push, pop, get, keys... Pop list/table, push derived data. Iteration map, filter, reduce, each... Pop a list and a block, push a new list. Variables set, @ (fetch)... Pop value + name to store; @name to push. I/O print, println, read... Print stack top; read stdin into stack. Filesystem ls, cd, pwd, mkdir, remove, mv... Work on paths (strings) and file tables. OS related tk, start, help... Binded with the OS behaviours.
External Commands
To escape into the underlying OS, prefix with $.
- Magic behavior
- If a string is on top of the stack, SunnyShell feeds it as stdin to the external process.
$ git status # Runs git, pushes stdout as a single string "Hello" $ cowsay # Pipes "Hello" into cowsay's stdin
Variables & Flow Control
Variables
Set a variable by pushing a name and a value, then using set:
"my_files" ls set # Stores the ls-result into 'my_files' @my_files length # Fetches the variable, computes its length
Conditionals (if)
{ @usage 80 > } # Condition (pushes boolean)
{ "Warning!" println } # Then
{ "All good" println } # Else
if # Executes conditionally
Loops
each- Pop a list and a block. Execute block for each item.
while- Pop a condition-block and a body-block. Loop until false.
# Print every file name in the current directory
ls { name println } each
Functions (def)
Define a new word using : (colon) and def. The function operates on the stack when called.
: double { 2 mul } def
5 double println # Prints 10
Working with the Filesystem (STRUCTURED!)
Because ls returns structured tables, filtering is type-safe and readable.
# Find all files > 1MB, sort by size descending, take top 3
ls
{ size 1048576 gt } filter
{ size } sort-desc # 'sort-desc' pops a list and a key-block
3 take
{ name println } each
No ls -l | grep ... | awk ... in sight.
Robust Error Handling
Errors don't crash the world. Use try and catch with blocks:
try {
$ rm -rf /important
} catch {
"Failed to delete: " swap + println # Catch block receives the error string
}
Examples
Hello, World!
"Hello, World!" println
Zombie-process Cleaning
This program will find any running zombie processes and terminate them. If no zombie processes are found, it returns with error code 0. If it does find any, it terminates them directly and returns with error code 1 after all of them have been terminated.
#!/usr/bin/env sunnyshell
# kill-zombies.s2s
false "%debug-mode" set
"Starting zombie hunt..." println
# 1. Run ps, split stdout into a list of lines
$ ps aux lines -> "raw_lines" set
# 2. Filter lines containing 'Z' (zombie state)
@raw_lines { "Z" contains? } filter -> "zombie_lines" set
# 3. If none, exit early
@zombie_lines length 0 eq {
"No zombies found. Peace." println
exit 0
} {
@zombie_lines length "Zombies found: " swap + println
} if
# 4. Extract PIDs (assumes PID is the 2nd column in `ps aux`)
@zombie_lines
{ split-whitespace } map # Convert each line to a list of columns
{ [1] get } map # Get the 2nd column (PID)
-> "pids" set
# 5. Kill them with extreme prejudice
@pids {
dup "Killing PID " swap add println
tk -regardless -forcibly
# Pop the top of the stack and parse it as an integer,
# then terminate the process with that PID.
# The two switches indicate whether to ignore if the operation of this process
# is already done and to force close.
} each
"Operation completed." println
exit 1
The Interactive Experience
The REPL (Read-Eval-Print Loop) is built for visibility:
- Prompt:
Current_User current/dir !> - Stack Viewer: After every command, it shows the top 5 stack items with indices. You can also set "%debug-mode" to false to cancel this feature.
- Tab Completion: Completes built-in words, variable names (@), and file paths.
- History: Persistent across sessions.
Interactive debugging:
Admin ~/ !> ls
Stack: [0] [Table(name="docs", size=4096), Table(name="notes.txt", size=120)]
Admin ~/ !> { size 100 > } filter
Stack: [0] [Table(name="docs", size=4096)]
Why This Breaks the Mold
Feature Bash / Cmd / PowerShell SunnyShell
Data Model Strings or heavy .NET objects Stacked, immutable, lightweight tables/lists
Parsing Output grep, awk, sed hell First-class filters (filter, map) on typed data
Syntax Infix / Prefix (confusing flags) Postfix (RPN) – deterministic, no operator precedence
Pipes Byte-streams (|) No pipes. Data sits on the stack, ready for the next word.
Variables $var everywhere, quoting chaos set to store, @ to fetch (unambiguous)
Control Flow if [ ... ]; then ... Blocks on the stack ({...} {...} if)
External Calls $(cmd) spawning subshells $ cmd with optional stdin push from stack
Conceptual Installation
# Install via Cargo (Rust) or a single static binary $ curl -sf https://s2.shell.abovesolstice/install | sh $ sunnyshell PrySigneToFry ~/ >
Summary
SunnyShell isn't just a shell—it's a new way of thinking about system interaction. By making data explicit and operations sequential, it eliminates entire classes of bugs (word-splitting, globbing, ambiguous redirects) that have plagued terminal users for decades.
Fun Facts
This is also designed by the author's timeline #284436, and also appears in timeline #811047 and #3018472.