# LSH — Lilush Shell Scripts

## Overview

LSH is the scripting format for the Lilush shell. While `.lsh` scripts may
look similar to bash at a glance, they are **not bash scripts** — the syntax
is simpler and more limited.

## Running scripts

Three ways to run an `.lsh` script:

```bash
# Via the lsh symlink (preferred)
lsh /path/to/script.lsh arg1 arg2

# Via shebang (make script executable first)
chmod +x script.lsh
./script.lsh arg1 arg2

# From within the interactive shell
run_script /path/to/script.lsh
```

For the shebang approach, the first line should be:

```
#!/usr/bin/lsh
```

## Script arguments

Arguments are exposed as environment variables, accessible with the
standard `${VAR}` expansion:

| Variable | Meaning |
|----------|---------|
| `${0}` | Absolute path to the script |
| `${1}`, `${2}`, ... | Positional arguments |
| `${#}` | Argument count (excludes script path) |

Example:

```
#!/usr/bin/lsh
echo "Running: ${0}"
echo "First arg: ${1}"
echo "Got ${#} arguments"
```

## Line processing

Each line of an `.lsh` script is parsed and executed independently.

- Empty lines are skipped
- Lines starting with `#` are skipped (comments, shebangs)
- All other lines are parsed as shell commands
- Execution stops on the first line that returns a non-zero exit code

There are no multi-line constructs — every command must fit on a single line.

## Supported syntax

### Pipes

```
cat /etc/hosts | grep localhost | wc -l
```

Commands can be chained with `|`. Standard output of each command feeds
into the standard input of the next.

### Operators

```
mkdir -p /tmp/test && echo "created"
ls /nonexistent || echo "not found"
echo one ; echo two ; echo three
```

- `&&` — run next command only if previous succeeded (exit code 0)
- `||` — run next command only if previous failed (exit code != 0)
- `;` — run next command regardless of previous exit code

Operators and pipes can be combined:

```
cat file.txt | grep pattern && echo "found" || echo "not found"
```

### Redirects

```
sort < unsorted.txt
echo hello > output.txt
echo hello >> log.txt
cmd err> errors.log
cmd all> combined.log
cmd > out.txt err> err.txt
sort < input.txt > sorted.txt
```

| Operator | Stream | Mode | Notes |
|----------|--------|------|-------|
| `< file` | stdin | read | First command in pipeline only |
| `> file` | stdout | truncate | Any command in pipeline |
| `>> file` | stdout | append | Any command in pipeline |
| `err> file` | stderr | truncate | Any command in pipeline |
| `err>> file` | stderr | append | Any command in pipeline |
| `all> file` | stdout + stderr | truncate | Any command in pipeline |
| `all>> file` | stdout + stderr | append | Any command in pipeline |

`err>` and `all>` require a word boundary — `stderr>` is parsed as the
argument `stderr` followed by `>`, not as `err>`.

Multiple redirects can be combined on a single command:

```
cmd < infile >> outfile err> errfile
```

Conflicting redirects (e.g. `> file all> file`, or duplicate `err>`) are
rejected with an error.

### Environment variables

```
echo "Home is ${HOME}"
echo "Path is ${PATH}"
setenv MY_VAR=hello
echo "${MY_VAR}"
```

Variables use the `${NAME}` syntax. The bare `$NAME` form is **not supported**.

### Home directory expansion

`~` at the start of a word expands to `$HOME`:

```
ls ~/documents
cat ~/.config/lilush/init.lsh
```

### Command substitution

```
echo "Current directory: $(pwd)"
echo "Today is $(date +%Y-%m-%d)"
```

`$(command)` executes a command and substitutes its output (trailing
newlines stripped).

### Secret variable shorthand

In LSH scripts, a bare line with a variable name followed by a `mneme://secrets/` URI
is treated as a shorthand for `setenv`:

```
# These two lines are equivalent:
DB_PASSWORD mneme://secrets/db_password
setenv DB_PASSWORD=mneme://secrets/db_password
```

This only triggers when the first token is a valid environment variable name
(`[A-Za-z_][A-Za-z0-9_]*`) and the second token starts with `mneme://secrets/`.
Regular commands that happen to receive a `mneme://` argument are not affected.

This shorthand is commonly used in `~/.config/lilush/init.lsh` to declare
which environment variables are backed by local encrypted secrets. See the
[Local Secrets](SHELL.md#local-secrets) section in the Shell docs
for details on storing, fetching, and managing these secrets.

### Quoting

```
echo 'literal ${VAR} not expanded'
echo "expanded: ${HOME}"
echo "file has spaces.txt"
```

- Single quotes: `${VAR}` not expanded, spaces preserved. Note: `$(cmd)`
  substitution occurs before quote processing and is always expanded.
- Double quotes: `${VAR}` and `$(cmd)` are expanded, spaces preserved

## Builtins available in scripts

Scripts have access to a subset of shell builtins. The following builtins
are handled at the script level and work in all contexts:

`rehash`, `alias`, `unalias`, `run_script`, `pyvenv`, `prompt`, `theme`, `secrets`

All other builtins (`ls`, `cd`, `mkdir`, `rm`, `kat`, `setenv`, `unsetenv`,
`envlist`, `ps`, `netstat`, `dig`, `history`, `files_matching`, `exec`,
`job`, `notify`, and others) are dispatched through the standard builtin
table and are also available.

External commands found in `${PATH}` work as expected.

## What LSH is NOT

If you're coming from bash, here's what to leave at the door:

### No glob / pathname expansion

```
# DOES NOT WORK:
ls *.txt
rm /tmp/test-[0-9]*

# Instead, use `files_matching` or explicit paths:
files_matching txt ls
```

`*`, `?`, `[...]` are not expanded. Use the `files_matching` builtin
or explicit paths.

### No control flow

There is no `if`/`then`/`else`, `for`, `while`, `case`, or `select`.
Each line is an independent command. Use `&&` and `||` for simple
conditional chaining.

### No functions

You cannot define shell functions. Factor reusable logic into separate
`.lsh` scripts and call them with `run_script`, or write it in Lua.

### No arithmetic

```
# DOES NOT WORK:
echo $((2 + 2))
i=$((i + 1))
```

There is no arithmetic expansion. Use external tools or Lua for math.

### No parameter expansion modifiers

```
# NONE OF THESE WORK:
${VAR:-default}
${VAR:=value}
${VAR#prefix}
${VAR%suffix}
${#VAR}
```

Only plain `${NAME}` is supported.

### No here-documents

```
# DOES NOT WORK:
cat <<EOF
hello
EOF
```

### No background execution operator

```
# DOES NOT WORK:
long_command &
```

The `&` operator is not supported. Use the `job` builtin for background
tasks in interactive mode.

### No mid-line comments

```
# This is a comment (works — start of line)
echo hello  # this is NOT a comment (the # is passed to echo)
```

Comments only work at the start of a line.

## When to use LSH vs Lua

LSH scripts are good for:

- Sequences of shell commands with simple conditional chaining
- Automation tasks that are mostly about running external programs
- Configuration scripts (like `~/.config/lilush/init.lsh`)

For anything requiring logic, loops, data structures, or error handling,
write a Lua script instead and run it with `lilush script.lua`.
