Skip to content

fix(grep): apply -m per file, as GNU does - #2487

Merged
chaliy merged 1 commit into
mainfrom
claude/pensive-hypatia-yl92dr
Sep 29, 2026
Merged

chaliy merged 1 commit into
mainfrom
claude/pensive-hypatia-yl92dr

Conversation

@chaliy

@chaliy chaliy commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

What changed

GNU's -m NUM stops reading each file after NUM matching lines — the budget is per operand. Bashkit accumulated matches across operands and broke out of the file loop once the total was spent, so a file that used up the budget silenced every file after it.

The per-file counter that drives output already existed (match_count), so the budget checks now read it instead of the cross-file total_matches, and that accumulator is gone along with the cross-file break.

Why

Found while reviewing #2485. It was out of scope there — that PR is about -m0 operand handling, this is the accumulator — so it was flagged for follow-up rather than folded in. This is that follow-up, and it removes the last remaining divergence from GNU in the -m family.

Before / After

Verified against GNU grep 3.11, with p.txt = foo1 foo2 foo3 and q.txt = foo4 foo5:

command GNU 3.11 bashkit before bashkit after
grep -m1 foo p.txt q.txt p.txt:foo1
q.txt:foo4
p.txt:foo1 ✅ matches
grep -c -m1 foo p.txt q.txt p.txt:1
q.txt:1
p.txt:1 ✅ matches
grep -v -m1 foo v.txt q.txt v.txt:bar (differed) ✅ matches

The -c case is the clearest symptom: a count row for the second operand simply disappeared, so grep -c under -m silently under-reported which files were searched at all.

Differential sweep — 275 cases (-m0/-m1/-m2/-m5 × -q/-c/-l/-L/-v/-n/-o/-i/-H/-h × present, multiple, missing and mixed operands), byte-compared against GNU grep 3.11:

divergent lines
before #2485 33
after #2485 6
this PR 0
cases: 275
IDENTICAL TO GNU GREP

Risk

  • Low
  • The change is confined to which counter the -m budget consults. -m without multiple operands is unaffected, and -L keeps the exemption added in fix(grep): skip operands for quiet zero match limit #2485. No existing test encoded the cumulative behaviour — all 63 builtins::grep unit tests and 61 grep integration/spec tests passed unchanged before the new spec cases were added.
  • Two max_reached assignments became dead once the budget was per-file (their break already leaves the loop). Removed, with a note that the flag now exists only so the -o closure can signal out of itself — caught by clippy -D warnings, not by hand.

Checklist

  • Tests added or updated — two spec cases: one pinning per-file behaviour for plain -m1/-m2 across two operands, one covering -c and -v. Each spec block gets a fresh VFS, so both create their own inputs.
  • Backward compatibility considered — this is a Bash-parity correction; the previous behaviour was a bug, and the new behaviour is what GNU does

cargo fmt --check, cargo clippy --features http_client,ssh,sqlite -- -D warnings, 63 unit + 61 integration/spec tests all green.

https://claude.ai/code/session_01DyKfW3rVZUrUKzrCLpurco


Generated by Claude Code

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit 634fdda Commit Preview URL

Branch Preview URL
Sep 29 2026, 11:55 AM

@chaliy
chaliy force-pushed the claude/pensive-hypatia-yl92dr branch from 85c56ec to 2060150 Compare September 29, 2026 11:39
GNU's -m NUM stops reading *each file* after NUM matching lines; the budget is
per operand. Bashkit accumulated matches across operands and broke out of the
file loop once the total was spent, so a file that used up the budget silenced
every file after it. Verified against GNU grep 3.11:

  $ grep -m1 foo p.txt q.txt
  p.txt:foo1
  q.txt:foo4        # bashkit stopped after p.txt

  $ grep -c -m1 foo p.txt q.txt
  p.txt:1
  q.txt:1           # bashkit omitted this row entirely

Same shape for -v. The per-file counter that drives output already existed
(match_count), so the budget checks now read it instead of the cross-file
total, and the total is gone along with the cross-file break.

This removes the last divergence found while reviewing #2485. The 275-case
differential sweep (-m0/-m1/-m2/-m5 x -q/-c/-l/-L/-v/-n/-o/-i/-H/-h x present,
multiple, missing and mixed operands) now matches GNU grep 3.11 byte for byte:
33 divergent lines before #2485, 6 after it, 0 now.

Two max_reached assignments became dead once the budget was per-file, since
their break already leaves the loop. Removed, with a note that the flag now
exists only so the -o closure can signal out of itself.

Claude-Session: https://claude.ai/code/session_01DyKfW3rVZUrUKzrCLpurco
@chaliy
chaliy force-pushed the claude/pensive-hypatia-yl92dr branch from 2060150 to 634fdda Compare September 29, 2026 11:54
@chaliy
chaliy merged commit 263e817 into main Sep 29, 2026
44 checks passed
@chaliy
chaliy deleted the claude/pensive-hypatia-yl92dr branch September 29, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant