Benchmark harness that uses LLM agents to solve shell scripting tasks in both Bash and Lush, then compares correctness and code quality. - CLI with run, run-all, list-tasks, report, and export commands - Agent loop with retry support via Anthropic Claude provider - Test harness executing solutions in sandboxed subprocesses - LLM-driven questionnaire for subjective code quality evaluation - HTML report export with charts (matplotlib) - 8 Category A tasks (write-from-scratch in both languages) - 4 Category B tasks (verify provided Bash, convert to Lush) - Lush language reference for agent context
247 lines
5.7 KiB
Markdown
247 lines
5.7 KiB
Markdown
# 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
|
|
})
|
|
```
|
|
|