diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 3026d5d41..8eb8ede92 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -171,11 +171,18 @@ impl StringOp { let mut prev_is_escaped = false; for curr in pattern_chars { match curr { - // Carets are interpreted literally, unless used as character class negation "[^a]" - '^' if prev_is_escaped || !matches!(prev, '\\' | '[') => { - re_string.push_str(r"\^"); + '^' => match (prev, prev_is_escaped) { + // Start of a capturing group + ('(', true) + // Start of an alternative pattern + | ('|', true) + // Character class negation "[^a]" + | ('[', false) + // Explicitly escaped caret + | ('\\', false) => re_string.push(curr), + _ => re_string.push_str(r"\^"), } - char => re_string.push(char), + _ => re_string.push(curr), } prev_is_escaped = prev == '\\' && !prev_is_escaped; diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 193737d10..4c0640e30 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -282,6 +282,14 @@ fn test_regex() { .args(&["a^b", ":", "a\\^b"]) .succeeds() .stdout_only("3\n"); + new_ucmd!() + .args(&["b", ":", "a\\|^b"]) + .succeeds() + .stdout_only("1\n"); + new_ucmd!() + .args(&["ab", ":", "\\(^a\\)b"]) + .succeeds() + .stdout_only("a\n"); new_ucmd!() .args(&["a$b", ":", "a\\$b"]) .succeeds()