Skip to content

docs(rules): grep -r on a single file omits the filename on GNU, not BSD - #2334

Open
laurigates wants to merge 1 commit into
mainfrom
docs/shell-scripting-grep-h-portability
Open

docs(rules): grep -r on a single file omits the filename on GNU, not BSD#2334
laurigates wants to merge 1 commit into
mainfrom
docs/shell-scripting-grep-h-portability

Conversation

@laurigates

Copy link
Copy Markdown
Owner

What

Adds a grep row and a subsection to shell-scripting.md's existing GNU vs BSD Tool Differences section.

grep -r on a single file argument prints line:text on GNU and file:line:text on BSD. A guard that greps a file list and parses FILE:LINE:TEXT therefore works on macOS and silently mis-parses on Linux whenever the list happens to hold one entry — the parse shifts a field and reads the line number as the filename.

grep -rn  -F "$pattern" "${files[@]}"   # GNU, one file: "2:gh api ..."
grep -rHn -F "$pattern" "${files[@]}"   # both:          "./path/run.sh:2:gh api ..."

Distilled from a live break, not a hypothetical

scripts/check-dead-api-endpoints.sh (#2321) was 17/17 green on macOS and failed one assertion on its first Linux CI run, reporting FILE=2.

The part worth writing down is that a fixture cannot catch this class — the fixture runs on whichever grep the author has, so the local suite is green by construction. Running on both platforms is the only detector. That is also why it is stated as an unconditional rule rather than a "when you have multiple files" caveat: "expect one file" is doing load-bearing work in the buggy version, and a single-entry list is exactly the trigger.

It sits beside the two BSD date traps because it is the same shape — an implementation-dependent default where the portable form costs one flag.

Cost

None on the always-loaded surface. shell-scripting.md is path-scoped to **/*.sh and scripts/**, so it loads only when shell is in play. check-context-engineering.py --strict still exits 0.

modified: / reviewed: bumped, per the frontmatter convention this repo's own check-docs-index.sh Check 5 enforces.

A guard that greps a file list and parses FILE:LINE:TEXT is correct on
macOS and silently mis-parses on Linux whenever the list happens to hold
ONE entry: GNU grep drops the filename prefix for a single file argument
while BSD keeps it, so the parse shifts a field and reads the line number
as the filename.

Distilled from a live break rather than a hypothetical:
scripts/check-dead-api-endpoints.sh was 17/17 green locally and failed one
assertion on its first Linux CI run, reporting FILE=2. A fixture cannot
catch this class — the fixture runs on whichever grep the author has — so
the rule states plainly that running on both platforms is the only detector
and -H is the one-flag fix.

Lands in shell-scripting.md's existing GNU-vs-BSD section beside the two
BSD date traps, which are the same shape: an implementation-dependent
default where the portable form costs one flag. The file is path-scoped to
**/*.sh and scripts/**, so this adds nothing to the always-loaded surface;
check-context-engineering.py --strict still exits 0.
@laurigates laurigates added the documentation Improvements or additions to documentation label Aug 8, 2026
@laurigates laurigates self-assigned this Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant