# Issue #10 — Interactive command execution **Status:** open ## The two modes Lush has two distinct ways to run external commands: 1. **Captured** — backtick syntax, pipes stdout/stderr, returns a table: ```lua local r = `ls -l` r.code -- exit code r.stdout -- captured stdout r.stderr -- captured stderr ``` 2. **Interactive** — bare command (no backticks), inherits the terminal, the process owns stdin/stdout/stderr directly: ``` > ls -l -- output goes straight to terminal > vim foo.lua -- full tty control, works properly > ssh user@host -- interactive session ``` This mirrors how traditional shells work: commands run interactively by default, and you explicitly capture output when you want it (in bash: `var=$(cmd)`; in lush: `` var = `cmd` ``). ## Syntax — bare-word REPL fallback If a line fails to parse as valid Lua, try to interpret it as an interactive shell command: ``` > vim foo.lua -- not valid Lua → runs as shell command > ls -la -- not valid Lua → runs as shell command > local x = 1 -- valid Lua → runs as Lua > x = `ls`.stdout -- valid Lua → runs as Lua (captured) ``` This only applies in the REPL. In scripts, use backtick syntax for captured commands (interactive commands in scripts are a separate question — possibly via `!` prefix or `exec()`, deferred). ## Return value and `_` Interactive commands can't capture stdout/stderr (the process owns the terminal). However, the exit code is still useful. After an interactive command completes, lush assigns a result table to the global `_`: ``` > ls -l (output appears directly in terminal) > _.code 0 ``` The table has the same shape as a captured command result, but with empty stdout/stderr: ```lua {code = , stdout = "", stderr = ""} ``` This keeps the interface consistent — `_` always has `.code`, `.stdout`, `.stderr`, whether the command was interactive or captured. Code that only checks `.code` works with both. ### Why `_`? - `_` is largely unused in Lua beyond the community convention of throwaway variables in unpacking (`local _, b = f()`) - This convention is safe here: backtick commands return a hashmap table, not a sequence, so `local _, x = \`cmd\`` isn't meaningful anyway (you'd use `\`cmd\`.stdout`) - Bash uses `$?` for the last exit code — `_` serves a similar role ## Runtime implementation Add `luaB_interactive` to `lcmd.c`, registered as `__interactive` global: - `fork()` without creating any pipes - Child: restore `SIG_DFL` for `SIGINT`/`SIGQUIT`, then `execvp()` (inherits parent's stdin/stdout/stderr) - Parent: ignore `SIGINT`/`SIGQUIT` (so Ctrl-C goes to child, not lush), then `waitpid()` and return result table - Reuses existing `parse_argv()` for command string tokenization ## REPL implementation In `lua.c`'s `loadline()`, after both `addreturn()` and `multiline()` fail: 1. Get the raw input line 2. Pass it to `__interactive(line)` 3. Assign the result to `_` 4. Continue the REPL loop (don't report a Lua syntax error) ## Open questions - Should `_` be set after captured (backtick) commands too? (So `_.code` always reflects the most recent command regardless of mode.) - Should the bare-word fallback support `${}` interpolation? (Probably not in the REPL fallback — the line isn't parsed by the lexer at all, it's just a raw string.) - Job control: should Ctrl-Z suspend the child and return to lush? Requires `tcsetpgrp()` / process group work. Defer to a later issue. - How to handle bare commands in scripts (not the REPL)? Defer — `!` prefix or `exec()` builtin are options for later. ## Files touched | File | Description | |------|-------------| | `lcmd.c` | Add `luaB_interactive` — fork/exec without pipes, return result table | | `lcmd.h` | Declare `luaB_interactive` | | `linit.c` | Register `__interactive` global in `opencommand()` | | `lua.c` | REPL fallback: on parse failure, try as interactive command, assign result to `_` |