Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions src/uu/expr/src/syntax_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,6 @@
let mut is_start_of_expression = true;
let mut after_anchor_caret = false;

// All patterns are anchored so they begin with a caret (^)
if pattern_chars.peek() != Some(&'^') {
re_string.push('^');
}

while let Some(curr) = pattern_chars.next() {
let curr_is_escaped = prev == '\\' && !prev_is_escaped;
let mut next_is_start = false;
Expand Down Expand Up @@ -425,7 +420,9 @@

let re_string = transpile_bre_to_ere(&pattern_str)?;

RegexBuilder::new(&format!("(?s){re_string}"))
// Patterns are anchored at the start of the string. The group makes the
// anchor apply to every top-level `\|` alternative, not just the first one.
RegexBuilder::new(&format!("(?s)^(?:{re_string})"))
.oniguruma_mode(true)
.leftmost_longest(true)
.build()
Expand Down Expand Up @@ -1504,6 +1501,27 @@

let result = evaluate_match_expression(b"world".to_vec(), b"world$".to_vec()).unwrap();
assert_eq!(result.eval_as_string(), b"5");

// Every top-level alternative is anchored, not just the first one
let result = evaluate_match_expression(b"xab".to_vec(), b"a\\|b".to_vec()).unwrap();
assert_eq!(result.eval_as_string(), b"0");

let result = evaluate_match_expression(b"xab".to_vec(), b"^a\\|b".to_vec()).unwrap();
assert_eq!(result.eval_as_string(), b"0");

let result = evaluate_match_expression(
b"evil status".to_vec(),
b"start$\\|stop$\\|status$".to_vec(),
)
.unwrap();
assert_eq!(result.eval_as_string(), b"0");

let result =
evaluate_match_expression(b"zabc".to_vec(), b"x\\|\\(abc\\)".to_vec()).unwrap();

Check warning on line 1520 in src/uu/expr/src/syntax_tree.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'zabc' (file:'src/uu/expr/src/syntax_tree.rs', line:1520)
assert_eq!(result.eval_as_string(), b"");

let result = evaluate_match_expression(b"bx".to_vec(), b"a\\|b".to_vec()).unwrap();
assert_eq!(result.eval_as_string(), b"1");
}

#[test]
Expand Down
32 changes: 32 additions & 0 deletions tests/by-util/test_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,38 @@ fn test_regex_trailing_backslash() {
.stderr_only("expr: Trailing backslash\n");
}

#[test]
fn test_regex_alternation_is_anchored() {
new_ucmd!()
.args(&["xab", ":", "a\\|b"])
.fails_with_code(1)
.stdout_only("0\n");
new_ucmd!()
.args(&["xab", ":", "^a\\|b"])
.fails_with_code(1)
.stdout_only("0\n");
new_ucmd!()
.args(&["file:///etc/shadow#ssh://", ":", "https://\\|ssh://"])
.fails_with_code(1)
.stdout_only("0\n");
new_ucmd!()
.args(&["evil status", ":", "start$\\|stop$\\|status$"])
.fails_with_code(1)
.stdout_only("0\n");
new_ucmd!()
.args(&["zabc", ":", "x\\|\\(abc\\)"])
.fails_with_code(1)
.stdout_only("\n");
new_ucmd!()
.args(&["match", "xab", "a\\|b"])
.fails_with_code(1)
.stdout_only("0\n");
new_ucmd!()
.args(&["ssh://host", ":", "https://\\|ssh://"])
.succeeds()
.stdout_only("6\n");
}

#[test]
fn test_regex_caret() {
new_ucmd!()
Expand Down
Loading