# Lush Lush is a shell built on [Lua 5.5.0](https://www.lua.org/). It extends Lua with shell-oriented features — backtick commands, pipes, globbing, interpolation, environment variables — while remaining a fully compatible Lua 5.5 interpreter. Based on [Lua](https://www.lua.org/) by R. Ierusalimschy, L. H. de Figueiredo, W. Celes (PUC-Rio). ## Quick Start Build and run: ```sh make ./lush ``` ## Language Guide Lush is Lua with shell extensions. Every valid Lua program runs unchanged. The additions are described below. ### Backtick Commands Execute shell commands with backticks. Each command runs in its own child process. Stdout and stderr are captured and returned as a table with `code`, `stdout`, and `stderr` fields. ```lua local r = `echo hello` print(r.stdout) -- "hello\n" print(r.code) -- 0 local r = `ls nonexistent` print(r.code) -- non-zero print(r.stderr) -- error message ``` Empty backticks return `{code=0, stdout="", stderr=""}`. A command killed by a signal returns exit code 128 + signal number (e.g. 137 for SIGKILL). ### Interactive Commands Prefix a line with `!` to run a command in a child process with stdout and stderr inherited from the terminal. The result is stored in `_`. ```lua !ls -la print(_.code) -- 0 !sh -c "exit 42" print(_.code) -- 42 ``` Works anywhere a statement is valid — inside functions, loops, if-blocks. ### Pipes Use `|` to connect commands in a pipeline. Each stage runs in its own process with stdout piped to the next stage's stdin. ```lua local r = `echo hello | tr a-z A-Z` print(r.stdout) -- "HELLO\n" !ps aux | grep lush | head -5 ``` The exit code comes from the last stage. Stderr from the last stage is captured (in backtick mode); stderr from middle stages goes to the terminal. Quoting (`"a|b"`, `'a|b'`, `a\|b`) treats `|` as literal. ### String Interpolation Three forms of interpolation inside backtick and interactive commands: `**${expr}` — Lua expression:** ```lua local name = "world" `echo ${name}` -- hello world `echo ${40 + 2}` -- 42 `echo ${name:upper()}` -- WORLD ``` `**$NAME` — Environment variable:** ```lua `echo $HOME` `echo $USER` ``` `**$(cmd)` — Subcommand substitution:** ```lua `echo today is $(date +%Y-%m-%d)` `echo $(echo $(echo nested))` -- nested substitution ``` Escape with backslash: `\$HOME` produces the literal string `$HOME`. ### Environment Variables Read and write environment variables directly from Lua using `$NAME` syntax: ```lua -- Read local home = $HOME local path = $PATH -- Write (visible to child processes) $MY_VAR = "hello" `echo $MY_VAR` -- hello -- Unset $MY_VAR = nil -- Numbers are coerced to strings $PORT = 8080 ``` ### Globbing Shell glob patterns are expanded inside backtick commands: ```lua `echo *.lua` -- expands to matching files `echo src/**/*.c` `echo file?.txt` -- single-character wildcard `echo config.[ch]` -- bracket patterns ``` Brace expansion: ```lua `echo {a,b,c}` -- a b c `echo file.{lua,c,h}` -- file.lua file.c file.h ``` Tilde expansion: ```lua `echo ~` -- /home/user `echo ~/projects` -- /home/user/projects ``` Quoting suppresses expansion: `"*.lua"`, `'*.lua'`, and `\*.lua` are all literal. ### Quoting Single quotes are literal (no escapes): ```lua `echo 'hello world'` `echo 'no $expansion here'` ``` Double quotes allow escapes and interpolation: ```lua `echo "hello world"` `echo "tab:\there"` `echo "path is $HOME"` `echo "sum is ${1 + 2}"` ``` Backslash outside quotes escapes the next character: ```lua `echo hello\ world` -- single argument: hello world ``` ### Builtins Built-in commands that affect the shell process: ```lua `cd /tmp` -- change directory `cd -` -- previous directory `cd` -- home directory `umask` -- query current umask `umask 0077` -- set umask ``` ### The `lush` Library Programmatic API for shell operations: ```lua lush.capture("echo hello") -- like backticks, returns {code, stdout, stderr} lush.run("echo hello") -- returns stdout with trailing newline stripped lush.interactive("ls") -- like !, returns result and sets _ lush.envget("HOME") -- read env var lush.envset("MY_VAR", "value") -- write env var ``` ### Extensibility **Custom commands** run in a child process (like any external command), so their output is captured and they support piping: ```lua lush.commands.greet = function(name, ...) print("hello " .. (({...})[1] or "world")) end `greet lush` -- hello lush `greet lush | tr a-z A-Z` -- HELLO LUSH ``` **Custom builtins** run in the current Lua process, so they can modify shell state (like `cd` does). They receive the raw command table and return a result table: ```lua lush.builtins.hello = function(cmd, name) return {code = 0, stdout = "hi " .. (name or "") .. "\n", stderr = ""} end `hello world` -- hi world ``` **Aliases:** ```lua lush.aliases.ll = "ls -la" `ll` -- runs: ls -la lush.aliases.ll = nil -- remove alias ``` ### Configuration Lush loads config files on interactive startup (suppressed with `-E`): - `$XDG_CONFIG_HOME/lush/config.lua` — main config - `$XDG_CONFIG_HOME/lush/config.d/*.lua` — additional configs, loaded alphabetically Use config files to set up aliases, custom commands, prompts, and environment. ### Prompt Customize the interactive prompt: ```lua -- In config.lua: _PROMPT = "lush> " _PROMPT2 = "...> " -- Dynamic prompt: _PROMPT = setmetatable({}, { __tostring = function() return os.date("%H:%M") .. " > " end }) ```