Files
lush/issues/13-shell-globbing.md
2026-03-10 21:35:48 +00:00

118 lines
4.2 KiB
Markdown

# Issue #13 — Shell globbing
**Status:** closed
**Blocked by:** #03, #04
## Problem
Commands inside backticks have no glob expansion. The argv parser treats `*`, `?`, and `{...}` as literal characters, so patterns like `ls *.lua` pass `*.lua` as a literal string to the command rather than expanding it to matching filenames.
```lua
-- currently: passes literal "*.lua" to ls
`ls *.lua`
-- expected: expands to matching files, like a shell would
`ls *.lua` -- becomes ls foo.lua bar.lua ...
```
## Glob patterns to support
| Pattern | Description | Example |
|---------|-------------|---------|
| `*` | Match any characters (not `/`) | `*.lua``foo.lua bar.lua` |
| `?` | Match single character | `?.c``a.c b.c` |
| `**` | Recursive directory match | `**/*.lua``src/a.lua lib/b.lua` |
| `{a,b}` | Brace expansion | `*.{o,h}``foo.o bar.h` |
| `[abc]` | Character class | `[Mm]akefile``Makefile makefile` |
### Brace expansion details
Brace expansion happens *before* glob matching (same as bash). It's purely textual — not a filesystem operation:
```
echo {o,h} → o h (two words)
echo hello{o,h} → helloo helloh (prefix attached)
echo hello{world,go} → helloworld hellogo (prefix attached)
echo *.{o,h} → (expand braces first, then glob each: *.o *.h)
echo hello{world, x} → hello{world, x} (space inside braces = literal, no expansion)
```
Rules:
- Braces with commas and no spaces expand: `{a,b,c}``a b c`
- Prefix/suffix attach to each alternative: `pre{a,b}suf``preasuf prebsuf`
- Spaces inside braces cancel the expansion (treated literally)
- Nested braces: not needed initially
## Implementation
Glob expansion should happen in `lcmd.c` during argv construction, after tokenizing but before `execvp()`. Each token that contains glob metacharacters (`*`, `?`, `[`, `{`) gets expanded into zero or more filenames.
### Approach
1. **Brace expansion** (textual, first pass): scan each token for `{a,b,...}` patterns. Expand into multiple tokens. No filesystem access needed.
2. **Glob matching** (filesystem, second pass): for each token containing `*`, `?`, or `[`, call `glob(3)` (POSIX) to expand against the filesystem. If no matches, keep the literal token (like bash default).
3. **`**` recursive matching**: `glob(3)` doesn't support `**` on all platforms. May need a custom recursive walk, or use `GLOB_ALTDIRFUNC` where available. Could also use `nftw()` + `fnmatch()`.
### Where it runs
Expansion happens inside `parse_argv()` or in a post-processing step after `parse_argv()` returns. Each original token potentially becomes multiple argv entries.
### Quoting suppresses globbing
Quoted strings are already literal in `parse_argv()`:
- `"*.lua"` → literal `*.lua` (no expansion)
- `'*.o'` → literal `*.o` (no expansion)
- `\*` → literal `*` (backslash escape)
This matches shell behaviour — quoting suppresses glob expansion.
## Edge cases
- **No matches**: keep the literal pattern (bash default behaviour with `nullglob` off)
- **Dot files**: `*` should not match files starting with `.` unless the pattern starts with `.` (standard shell convention)
- **Expansion in pipelines**: each pipeline stage gets its own glob expansion
- **Very large expansions**: `**/*` in a large tree could produce thousands of entries — may need a reasonable limit
## Tests
```lua
-- basic wildcard
local r = `echo *.lua`
-- stdout should contain space-separated .lua files (non-empty)
-- no match keeps literal
local r = `echo *.nonexistent_extension_xyz`
assert(r.stdout == "*.nonexistent_extension_xyz\n")
-- quoted glob is literal
local r = `echo "*.lua"`
assert(r.stdout == "*.lua\n")
-- single-quoted glob is literal
local r = `echo '*.lua'`
assert(r.stdout == "*.lua\n")
-- brace expansion
local r = `echo {a,b,c}`
assert(r.stdout == "a b c\n")
-- brace expansion with prefix
local r = `echo hello{world,there}`
assert(r.stdout == "helloworld hellothere\n")
-- brace with spaces = no expansion
local r = `echo {a, b}`
assert(r.stdout == "{a, b}\n")
-- ? single char match
-- [abc] character class
```
## Files to modify
| File | Change |
|------|--------|
| `lcmd.c` | Add brace expansion + glob expansion in argv construction |