SIGN IN SIGN UP

feat(grep): support -f/--file to read patterns from file (#327)

* fix(search-engine): group alternation inside -x line anchors

`lineRegexp` wrapped the pattern as `^PATTERN$`, which anchors only the
outer alternatives when PATTERN contains a top-level `|`: `a|b` became
`^a|b$` (i.e. "starts with a" OR "ends with b") instead of "the whole line
is a or b". Wrap in a non-capturing group, mirroring what the -w wrapper
already does.

Only observable once grep can produce multi-alternative patterns, which
`grep -f` does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DayCPuYZEmv4VszJXTHT3
Signed-off-by: Lars Trieloff <lars@trieloff.net>

* feat(grep): support -f/--file to read patterns from file

Adds GNU-compatible `-f FILE` / `--file=FILE`: patterns are read from FILE,
one per line, and OR-combine with `-e` patterns and with each other.

Forms accepted: `-f FILE`, `-fFILE`, `-vxf FILE`, `-vxfFILE`, `--file=FILE`,
`--file FILE`, and `-f -` (patterns from stdin, which then leaves nothing to
search on stdin).

GNU edge cases matched (verified against GNU grep 3.12):

- Empty pattern lines are real patterns that match every line; with -x they
  anchor to `^$` and match only blank lines.
- A pattern file's trailing newline terminates the last pattern rather than
  introducing an empty one; a `-e` operand's trailing newline does introduce
  one.
- An empty pattern file contributes no patterns. When the whole pattern list
  ends up empty, grep selects nothing and short-circuits: no output, no
  per-file `-c` counts, no "No such file or directory" diagnostics for the
  input files, exit 1. `-v` still selects every line, and `-L` still visits
  the files.
- A missing or unreadable pattern file is reported as
  `grep: FILE: No such file or directory` (or `Is a directory`) with exit 2,
  before any input file is searched. An empty file name reports
  `grep: : No such file or directory` rather than resolving against the cwd.
- stdin is a stream, so only the first `-f -` reads it; a repeated `-f -`
  hits EOF and contributes no patterns.
- `-P` refuses more than one distinct pattern, as GNU's PCRE backend does.
- `PATTERNS` operands are newline-separated lists, so a positional or `-e`
  pattern containing newlines is split into individual patterns too.

Alternatives are joined textually, so each pattern is compiled on its own
first (except for `-F`, whose patterns are escaped): otherwise a malformed
pattern swallows the separator and silently absorbs its neighbour, e.g.
`a\` + `banana` compiling as the literal `a|banana`.

The number of patterns read from files is bounded by `maxArrayElements`.

Unskips five busybox grep spec tests that were blocked on -f and on
newline-delimited patterns.

Closes #322.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DayCPuYZEmv4VszJXTHT3
Signed-off-by: Lars Trieloff <lars@trieloff.net>

---------

Signed-off-by: Lars Trieloff <lars@trieloff.net>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
L
Lars Trieloff committed
eaedb5bc34cffd2077b88bb1ccc48ea7e0545a48
Parent: 65dafd5
Committed by GitHub <noreply@github.com> on 8/6/2026, 2:03:54 AM