Skip to content

feat: add --must-match to exit non-zero when nothing matched - #347

Open
vlasky wants to merge 1 commit into
chmln:masterfrom
vlasky:feat/must-match
Open

feat: add --must-match to exit non-zero when nothing matched#347
vlasky wants to merge 1 commit into
chmln:masterfrom
vlasky:feat/must-match

Conversation

@vlasky

@vlasky vlasky commented Jul 29, 2026

Copy link
Copy Markdown

Implements #157.

Problem

sd exits 0 whether or not the pattern matched, mirroring sed. That makes a typo in the pattern indistinguishable from a file that genuinely needed no changes:

$ echo "foo" | sd 'typo' 'bar'
foo
$ echo $?
0

In a script or CI job, a vacuous edit passes silently. As noted on #157, this is troublesome enough that people fall back to sed to detect whether anything was replaced.

Change

An opt-in -m / --must-match flag that exits 2 when the pattern matched none of the input:

$ echo "foo" | sd -m 'typo' 'bar'
foo
$ echo $?
2

The default is deliberately unchanged. On #157 @CosmicHorrorDev wrote:

Just to be clear I wasn't thinking of changing the default behavior. We should typically default to matching sed's behavior which we do already here

and

I was thinking something along the lines of a --must-match flag

so this is that flag rather than a change of default.

Design points:

  • Exit 2, not 1. Errors keep 1, so a script can tell "your pattern found nothing" apart from "sd could not do its job". The man page's hardcoded EXIT STATUS section lists the new code.
  • The status describes the run, not each input. One matching file out of several is enough to succeed, which is what a sd -m ... *.rs invocation wants.
  • The flag only affects the exit status. Inputs that did match are replaced either way; nothing is made conditional or rolled back.
  • No extra work to detect a match. Replacer::replace already returns Cow::Borrowed when the pattern did not match and Cow::Owned otherwise, so the variant answers the question without a second scan. That contract is now documented on replace.

process_sources and its helpers return Result<bool> reporting whether anything matched, leaving the exit-status policy in the CLI.

Testing

11 new tests, taking the workspace from 48 to 60. Three at the library level for the returned bool, and eight CLI tests covering: default unchanged (exit 0 for both a matching and a non-matching run, file and stdin), --must-match with and without a match, stdin, -p, -A, a multi-file run where only one file matches, and that an error still exits 1 rather than 2. Each behavioural case is exercised through both the line-by-line and whole-file paths.

cargo fmt --check and cargo clippy --workspace --all-targets -- -D warnings are clean. gen/ completions and the man page are regenerated via cargo run -p xtask -- gen.

Notes

  • Documented under a new "Requiring a match" section in the README.
  • I have left CHANGELOG.md alone, since its history looks like maintainer-written release prep. Happy to add an entry if you would prefer it in the PR.
  • This shares a README conflict with feat: reject replacement references to undefined capture groups #346 (both append a section at the end); the code files merge cleanly. Whichever lands second, I will rebase.

sd exits 0 whether or not the pattern matched, mirroring sed. That makes a
typo in the pattern indistinguishable from a file that needed no changes,
which is a problem for scripts and CI where a vacuous edit passes silently.

Add an opt-in -m/--must-match flag that exits 2 when the pattern matched
none of the input. The default is unchanged, per the discussion in chmln#157.
Errors keep status 1 so a caller can tell "found nothing" apart from "could
not run", and the status covers the run as a whole, so one matching input
out of several is enough to succeed.

process_sources and its helpers now return whether anything matched, taken
from the Cow variant that replace already returns, so no extra scan is
needed. The exit-status policy stays in the CLI.

Closes chmln#157
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