# 15 — Shell builtins **Status:** done Some commands must run inside the shell process itself to have any effect. An external `cd` binary exists on most systems but it spawns a subprocess, changes directory there, and exits — leaving lush's own working directory unchanged. Lush only needs builtins for operations that **cannot be implemented in pure Lua or via external binaries** — things that require modifying the shell process state via syscalls with no Lua API. ## Assessment Every builtin from `man 1 builtin` was evaluated against two questions: 1. Can it be done in Lua? (e.g. `export` → `$VAR = "val"`, `source` → `dofile()`, `exit` → `os.exit()`) 2. Does the external binary work? (e.g. `/bin/pwd`, `/usr/bin/which`, `/usr/bin/kill`) If either answer is yes, it's not needed as a builtin. What remains: | Builtin | Why it must be a builtin | |---------|-------------------------| | **`cd`** | `chdir(2)` must happen in the lush process. No Lua API. | | **`exec`** | `execvp(2)` replaces the current process. `os.execute()` spawns a subprocess. No Lua equivalent. | | **`umask`** | `umask(2)` is per-process. The external `/usr/bin/umask` is a shell script that calls the shell's own builtin. No Lua API. | ## Builtins ### `cd [dir]` Change working directory via `chdir(2)`. - No argument: `cd $HOME` - `cd -`: change to `$OLDPWD` - Updates `$PWD` and `$OLDPWD` environment variables - Errors if directory doesn't exist or isn't accessible ### `exec command [args...]` Replace the shell process with the given command via `execvp(2)`. Does not return on success. On failure, prints an error and the shell continues. ### `umask [mode]` Get or set the file creation mask via `umask(2)`. - No argument: print current umask (octal) - With argument: set umask to the given octal value ## Design considerations - **Dispatch order:** When a command is entered (backtick or `!` prefix), check builtins *before* searching `$PATH`. A builtin named `cd` must intercept the command before it reaches `execvp`. - **Implementation:** Expose the syscalls to Lua (e.g. `os.chdir()`, `os.exec()`, `os.umask()`), then implement the builtins as Lua functions registered at startup. This keeps the C side minimal and lets users compose with the primitives directly. - **Interaction with backticks:** `` `cd /tmp` `` and `!cd /tmp` should both trigger the builtin, not spawn an external process. ## Open questions - Should builtins be overridable by the user? - Should there be a `builtin` command to bypass user overrides?