Skip to content

Fix --posix to disable the GNU regular expression escapes - #550

Merged
sylvestre merged 6 commits into
uutils:mainfrom
LoukasPap:issue542
Oct 19, 2026
Merged

sylvestre merged 6 commits into
uutils:mainfrom
LoukasPap:issue542

Conversation

@LoukasPap

@LoukasPap LoukasPap commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #542

Under --posix, GNU sed treats the GNU regular expression escapes as the literal character that follows the backslash. These are the operators \?, \+ and \|, the character classes \w, \W, \s and \S, and the empty-width matches \b, \B, \<, \>, \` and \'.

parse_regex_for_mode() now takes a POSIX parameter and emits the literal character for these escapes. The check comes before parse_char_escape(), so a GNU extension is never mistaken for a character escape.

BRE and ERE

The two modes need the opposite spelling of the same literal:

  • In BRE the character is emitted bare, because bre_to_ere() escapes the ERE metacharacters afterwards. Emitting \? would make bre_to_ere() turn it back into the operator.
  • In ERE \?, \+ and \| keep their backslash, since a backslash is already what makes them literals there. Emitting them bare would make them operators.

The remaining escapes are letters and punctuation with no meaning of their own, so they are emitted bare in both modes.

Bracket expressions

A backslash is only special inside a bracket expression when it starts a character escape. Elsewhere it is an ordinary member of the set and does not quote what follows, so [\w] holds a backslash and a w, and [a\]bc] ends at the first ]. A [ that does not open a POSIX class, collating symbol or equivalence is an ordinary member too, which also fixed [[x] and [x[].

Under --posix no backslash escape is recognized inside a bracket expression, so [\t] matches a backslash or a t while \t still matches a tab. The manual documents this for POSIXLY_CORRECT and basic regular expressions, but GNU applies it to --posix and to extended regular expressions as well.

The \b escape

parse_char_escape() no longer decodes \b as a backspace, because sed has no such escape. The manual omits it on purpose:

\b (backspace) was omitted because of the conflict with the existing "word boundary" meaning.

Removing it corrected four places at once: \b is a word boundary in a regular expression, and a bare b in the s replacement, in the y strings and in the text of a, i and c.

That exposed a further divergence in the s replacement and the y strings, which emitted the backslash along with the character. An escape sed does not recognize now stands for the bare character, as GNU sed does, so s/x/a\qc/ gives aqc and y/x/\b/ gives b instead of failing to compile.

Copilot AI lite review requested due to automatic review settings September 5, 2026 13:05
@codecov

codecov Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.93%. Comparing base (96afb29) to head (5891d66).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #550      +/-   ##
==========================================
+ Coverage   94.85%   94.93%   +0.08%     
==========================================
  Files          14       14              
  Lines        7230     7292      +62     
  Branches      432      434       +2     
==========================================
+ Hits         6858     6923      +65     
+ Misses        371      368       -3     
  Partials        1        1              
Flag Coverage Δ
macos_latest 96.46% <100.00%> (+0.07%) ⬆️
ubuntu_latest 96.40% <100.00%> (+0.07%) ⬆️
windows_latest 0.00% <0.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new integration tests don’t yet cover all GNU escapes called out by #542 (notably \+ and \|), reducing end-to-end regression protection for the claimed fix.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates the sed script regex parser so that when --posix is enabled, GNU regex escape extensions (e.g., \?, \+, \|, \w, \s, etc.) are treated as literals rather than GNU-specific operators/classes/anchors, aligning behavior with GNU sed and fixing #542.

Changes:

  • Thread context.posix into regex parsing (parse_regex_for_mode) and handle GNU escape extensions as literals under POSIX mode (with special handling for ERE metacharacters ? + |).
  • Add end-to-end regression tests validating --posix disables key GNU regex escapes, plus unit tests covering BRE vs ERE emission rules.
File summaries
File Description
tests/by-util/test_sed.rs Adds integration tests for --posix behavior around GNU regex escape extensions.
src/sed/delimited_parser.rs Implements --posix parsing behavior for GNU regex escapes and adds targeted unit tests.
src/sed/compiler.rs Passes context.posix into regex parsing for addresses and substitution patterns.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/sed/delimited_parser.rs Resolved
Comment thread tests/by-util/test_sed.rs Resolved
@LoukasPap

Copy link
Copy Markdown
Contributor Author

@sylvestre While working on this I noticed that \b, \<, \>, \` and \' are also broken without --posix, where they never match:

$ printf 'cat catalog\n' | sed 's/\<cat\>/X/g'
X catalog
$ printf 'cat catalog\n' | ./target/debug/sed 's/\<cat\>/X/g'
cat catalog

The fix looks small, and differs per escape. All three engines support \<, \>, \b, \A and \z natively, so no lookarounds are needed:

  • \< and \> already reach Regex::new() unchanged. They break only because NEEDS_RE in fast_regex.rs does not list them, so the pattern is taken for a literal string and remove_escapes() drops the backslash, leaving <cat>. Widening \\[AzBb] to \\[AzBb<>] is enough.
  • \b is decoded as a backspace by parse_char_escape(), so it has to be intercepted before that call and emitted unchanged.
  • \` and \' have no equivalent in the Rust engines and need translating to \A and \z.

This is the separate problem from this PR, so I left it out. Do you want to add it in this PR or open a separate issue for it?

Copilot AI review requested due to automatic review settings September 5, 2026 13:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The change is narrowly scoped, threads --posix consistently through the compiler, and includes both unit and integration tests covering the corrected behavior.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 21, 2026 12:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

POSIX escape handling remains incorrect inside bracket expressions and when ?, +, or | is the delimiter.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)

Comment thread src/sed/delimited_parser.rs Resolved
Comment thread src/sed/delimited_parser.rs Resolved
Comment thread tests/by-util/test_sed.rs Resolved
Copilot AI review requested due to automatic review settings September 24, 2026 12:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Two moderate regex parsing issues remain in delimiter and bracket-expression handling.

Review effort: Lite
Findings: None

Resolved since last review (1)

@github-actions

Copy link
Copy Markdown

GNU sed testsuite comparison:

Test results comparison:
  Current:   TOTAL: 65 / PASSED: 15 / FAILED: 42 / SKIPPED: 8
  Reference: TOTAL: 65 / PASSED: 14 / FAILED: 43 / SKIPPED: 8

Changes from main branch:
  TOTAL: +0
  PASSED: +1
  FAILED: -1

Test improvements (1):
  + xemacs

@LoukasPap
LoukasPap requested a review from sylvestre September 24, 2026 12:45
Copilot AI lite review requested due to automatic review settings October 5, 2026 21:14
@codspeed

codspeed Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 11 untouched benchmarks


Comparing LoukasPap:issue542 (5891d66) with main (b1ea0dc)

Open in CodSpeed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Moderate unresolved regex and bracket parsing issues remain.

Review effort: Lite
Findings: 2 Medium severity

Open (2)

Comment thread src/sed/delimited_parser.rs Resolved
Comment thread src/sed/delimited_parser.rs Resolved
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

GNU sed testsuite comparison:

Test results comparison:
  Current:   TOTAL: 65 / PASSED: 21 / FAILED: 36 / SKIPPED: 8
  Reference: TOTAL: 65 / PASSED: 18 / FAILED: 39 / SKIPPED: 8

Changes from main branch:
  TOTAL: +0
  PASSED: +3
  FAILED: -3

Test improvements (3):
  + posix-char-class
  + word-delim
  + xemacs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No unresolved review issues were identified.

1 open finding
1 resolved since last review

🧠 Review effort: Lite


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread src/sed/delimited_parser.rs Resolved
Comment thread src/sed/delimited_parser.rs Resolved
- Add a POSIX parameter to `parse_regex_for_mode()` that emits the literal
  character for `\?`, `\+`, `\|`, `\w`, `\W`, `\s`, `\S`, `\b`, `\B`, `\<`,
  `\>`, `` \` `` and `\'`
- Emit the character bare in BRE, where `bre_to_ere()` escapes it afterwards,
  and keep the backslash on `\?`, `\+` and `\|` in ERE
- Add unit and integration tests
A backslash is only special inside a bracket expression when it starts a character escape.  Elsewhere it is an ordinary member of the set and does not quote the character that follows, so `[\w]` matches a backslash or a w, and `[a\]bc]` ends at the first `]`.  This matches GNU sed in both basic and extended mode.
A '[' inside a bracket expression that does not open a POSIX class, collating symbol or equivalence is an ordinary member of the set. It was emitted together with the character that followed it, so the ']' in `[\[]` was swallowed and the expression was reported as unterminated.

sed has no \b character escape, so remove it from parse_char_escape. A backslash in `[\b]` is an ordinary member of the set, '\b' in a regular expression is a word boundary, and in text and transliteration strings it stands for a bare 'b'.

Disable the remaining GNU character escapes inside bracket expressions under --posix. They stay available outside them, as GNU sed does, so `[\t]` matches a backslash or a t while `\t` still matches a tab.
Copilot AI lite review requested due to automatic review settings October 11, 2026 13:16
@github-actions

Copy link
Copy Markdown

GNU sed testsuite comparison:

Test results comparison:
  Current:   TOTAL: 65 / PASSED: 21 / FAILED: 36 / SKIPPED: 8
  Reference: TOTAL: 65 / PASSED: 18 / FAILED: 39 / SKIPPED: 8

Changes from main branch:
  TOTAL: +0
  PASSED: +3
  FAILED: -3

Test improvements (3):
  + posix-char-class
  + word-delim
  + xemacs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Preserve \b backspace handling for non-regex syntax while applying POSIX behavior only to regex parsing.

0 open findings

1 resolved since last review

🧠 Review effort: Lite

Comment thread src/sed/compiler.rs Resolved
GNU sed drops the backslash of an escape it does not recognize, so do the same in the s replacement and in the y strings. The text of a, i and c already did.

This also makes `y/x/\b/` compile again. Now that \b is not a character escape it produced two characters instead of one, and the two y strings must have equal lengths.
Copilot AI lite review requested due to automatic review settings October 19, 2026 14:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

2 open issues

Review effort: Lite

Comment thread src/sed/delimited_parser.rs Resolved
Comment thread src/sed/delimited_parser.rs Resolved
@github-actions

Copy link
Copy Markdown

GNU sed testsuite comparison:

Test results comparison:
  Current:   TOTAL: 65 / PASSED: 21 / FAILED: 36 / SKIPPED: 8
  Reference: TOTAL: 65 / PASSED: 18 / FAILED: 39 / SKIPPED: 8

Changes from main branch:
  TOTAL: +0
  PASSED: +3
  FAILED: -3

Test improvements (3):
  + posix-char-class
  + word-delim
  + xemacs

parse_regex is only called by the unit tests, so gate it behind cfg(test). The compiler calls parse_regex_for_mode, which is given the character and --posix modes of the run, and a --posix path can no longer reach a function that hardcodes posix = false.

No backslash escape at all is recognized inside a bracket expression under --posix, not only the GNU ones, so say that instead.
Copilot AI lite review requested due to automatic review settings October 19, 2026 15:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

ERE delimiter escapes for |, ?, and + must preserve literal behavior.

No open issues 🎉

2 resolved since last review

Review effort: Lite

@LoukasPap
LoukasPap requested a review from sylvestre October 19, 2026 16:01
@github-actions

Copy link
Copy Markdown

GNU sed testsuite comparison:

Test results comparison:
  Current:   TOTAL: 65 / PASSED: 21 / FAILED: 36 / SKIPPED: 8
  Reference: TOTAL: 65 / PASSED: 18 / FAILED: 39 / SKIPPED: 8

Changes from main branch:
  TOTAL: +0
  PASSED: +3
  FAILED: -3

Test improvements (3):
  + posix-char-class
  + word-delim
  + xemacs

@sylvestre
sylvestre merged commit 3e86e42 into uutils:main Oct 19, 2026
31 checks passed
@sylvestre

Copy link
Copy Markdown
Contributor

Thanks for your PR

@LoukasPap

Copy link
Copy Markdown
Contributor Author

@sylvestre Thanks for the help! But I didn't squash. Do we want to?

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.

--posix should disable the GNU regular expression escapes (\? \+ \| \w \s)

3 participants