feat(grep): treat - operand as standard input - #331
Open
trieloff wants to merge 1 commit into
Open
Conversation
`grep PATTERN -` failed with "grep: -: No such file or directory" instead of reading stdin. GNU treats `-` as a FILE operand naming standard input, labelled `(standard input)` wherever a file name would appear. - `-` reads stdin and is labelled `(standard input)` in the multi-file prefix and in -l/-L/-c output; a lone `-` gets no prefix, and -h suppresses it like any other file name. - stdin is a stream: repeated `-` operands see it drained by the first, so `grep -c pat - -` prints `(standard input):N` then `(standard input):0`. - `-` bypasses glob expansion, -r recursion and --include/--exclude, which all filter on a file name stdin does not have. Under -r the file-name prefix is now forced only when a real path is searched, so `grep -r pat -` prints bare lines like GNU. - Empty stdin is a valid empty stream (exit 1, `-c` prints 0); absent stdin reads as empty, matching `cat -`. Unskips 4 busybox spec tests and corrects the reason on 2 that stay skipped for unrelated causes (spec-runner path rewriting, missing -s). All expectations verified against GNU grep 3.12; comparison fixtures are recorded from it and locked. Signed-off-by: Lars Trieloff <lars@trieloff.net>
|
@trieloff is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
grepdid not accept-as a FILE operand. Found while reviewing #327 (grep -f), which added-f -for reading patterns from stdin and made the neighbouring gap obvious. There is no issue for it.-is a POSIX-documented operand meaning standard input, and the busybox grep spec suite already exercises it — six cases were parked inspec-tests/grep/skips.tswith the reason "- stdin arg not supported".This PR implements the GNU semantics. Every expectation below was verified against GNU grep 3.12 (
/opt/homebrew/opt/grep/libexec/gnubin/grep), never macOS BSD grep.Details
The label. GNU prints
(standard input)wherever a real file name would appear — the multi-filefile:lineprefix,-l/-Llistings,-ccounts, and thefile-lineform of context lines. (Context output with-A/-B/-Cis not exercised in the new tests: just-bash omits GNU's--separator between context groups, a pre-existing divergence that predates this PR —grep -A1 pat f1 f2onmainalready omits it,-or no-.)A lone
-gets no prefix (one operand), and-hsuppresses it exactly as it does for files.-l/-Lstill print(standard input)under-h, since those are the output rather than a prefix.Stream semantics for repeated
-. stdin is a stream, so only the first-sees content; every later one reads EOF:Implemented by marking each queued
-entry withstdinAtEofat operand-collection time, so the parallel batch executor stays deterministic.-with-r. GNU only forces the file-name prefix under-rwhen recursion can actually descend into a directory, and stdin is never a directory:grep -r pat -prints bare lines, whilegrep -r pat - subprefixes both sources (two operands).showFilenamenow requires a real path target before-rforces the prefix. This deliberately leaves the pre-existinggrep -r pat single-file.txtdivergence (just-bash prefixes, GNU does not) untouched — out of scope here.-bypasses name-based filtering. Glob expansion, recursion and--include/--excludeall filter on a file name; stdin has none, so GNU applies none of them.printf 'apple\n' | ggrep --exclude='*' apple -still printsapple.Empty vs absent stdin. Empty stdin is a valid empty stream: no output,
-cprints0, exit 1 — same as GNU with< /dev/null. When just-bash has no stdin at all,-reads empty, consistent with howcat -already behaves in this repo.Exit codes. Unchanged rules, now fed by stdin too: 0 on match, 1 on none, 2 when any operand errored. A missing operand alongside
-still searches stdin and exits 2;-qshort-circuits to 0 on a match even then, per SUSv3 (busybox spec case atbusybox-grep.tests:55).Two adjacent pre-existing divergences are deliberately not addressed (both reproduce on
mainwithout-):grep pat - direxits 0 where GNU exits 2 (directory operands don't set the error flag), and-qsuppresses theNo such file or directorydiagnostic that GNU still prints to stderr — the new-qtest asserts just-bash's current (empty) stderr, and the corresponding fixture routes stderr to/dev/null, so neither pins the GNU wording.-His not implemented in just-bash at all (it is rejected as an unknown option, onmaintoo). Out of scope;-hinteraction is covered.Composition with #327 (
grep -f). Both-f -and a-operand consume stdin, and in GNU the first consumer wins — patterns are read before input, so-f -drains the stream and the-operand then reads EOF:The two PRs are independent (this one is based on
upstream/main, not on #327). #327 renamesfiles→operandsin the arg loop and introducesstdinUsedForPatterns; this PR adds alet stdinConsumed = falsenext to the operand-expansion loop. Whichever lands second needs one mechanical edit: initialisestdinConsumedfromstdinUsedForPatternsinstead offalse, which reproduces GNU's first-consumer-wins behaviour for free. No other overlap — #327 touches the pattern-collection half of the function, this PR touches the operand-expansion and per-file-read half.Composition with #314 (repeated
-e). #314 also renamesfiles→operandsand addscombinePatterns; it does not touch operand expansion,showFilenameor the per-file read. Both PRs delete fromspec-tests/grep/skips.ts, but from non-adjacent hunks (#314 removes the "Multiple -e patterns" block, this PR the "- for stdin argument position" block). Rebase is mechanical.Tests
GNU grep version used for every expectation and every fixture: GNU grep 3.12 (Homebrew,
/opt/homebrew/opt/grep/libexec/gnubin/grep).packages/just-bash/src/commands/grep/grep.stdin-operand.test.ts— 28 unit tests, 286 lines, full-string stdout/stderr assertions plus exit codes. Covers the exact repro, the(standard input)label across-n/-l/-L/-c/-o/-v,-h, repeated-(2× and 3×),-m,-rprefixing,--include/--excludeexemption,--terminator,-e,-as PATTERN, empty stdin, absent stdin, and the missing-operand +-qexit codes.packages/just-bash/src/comparison-tests/grep-stdin-operand.comparison.test.ts+fixtures/grep-stdin-operand.comparison.fixtures.json— 27 comparison cases / 27 fixtures, all"locked": trueso BSD-grep boxes cannot silently re-record them. The re-record command is documented in the test file header:packages/just-bash/src/spec-tests/grep/skips.ts— 4 busybox spec tests unskipped and now passing:grep - (specify stdin),grep - nofile (specify stdin and nonexisting file),grep -q - nofile (... match),grep -L exitcode 0 #2. Two entries stay skipped with corrected reasons:grep - infile(the spec runner rewritesinputto/tmp/input, so the file-name prefix differs — a runner artifact, not a grep bug) andgrep -s nofile -(-sis not implemented).Verification numbers on this branch:
pnpm typecheck— clean;pnpm lint(biome + workflow security +lint:banned) — clean;pnpm knip— clean (2 pre-existing configuration hints only).pnpm test:comparison— 36 files, 594 passed, 0 failed.pnpm test:run— 14342 passed, 97 skipped, 6 failed. The 6 failures are pre-existing python3/WASM sandbox tests (just-bash.bundle,code-exec-exploit-regression,defense-in-depth-independence,python-sqlite-information-disclosure×2,worker-protocol-runtime-desync); confirmed identical (6 failed / 26 passed across those 5 files) on a stashed, unmodified tree.Not touched:
-L's exit-code inversion (GNU keys-L's exit status off whether a line was selected, not off whether anything was printed) is being fixed separately. Every-Lcase here is one where both the current and the corrected rule agree.🤖 Generated with Claude Code
https://claude.ai/code/session_019DayCPuYZEmv4VszJXTHT3