Skip to content

Commit 07501be

Browse files
authored
expr: Escape anchor characters within pattern (#7842)
* expr: Escape anchor characters within the core pattern The anchor characters `^` and `$` are not considered special characters by `expr` unless they are used as expected on the start or end of the pattern.
1 parent ddf48fa commit 07501be

File tree

2 files changed

+55
-8
lines changed

2 files changed

+55
-8
lines changed

src/uu/expr/src/syntax_tree.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,32 @@ impl StringOp {
150150
let left = left?.eval_as_string();
151151
let right = right?.eval_as_string();
152152
check_posix_regex_errors(&right)?;
153-
let prefix = if right.starts_with('*') { r"^\" } else { "^" };
154-
let re_string = format!("{prefix}{right}");
153+
154+
// All patterns are anchored so they begin with a caret (^)
155+
let mut re_string = String::with_capacity(right.len() + 1);
156+
re_string.push('^');
157+
158+
// Handle first character from the input pattern
159+
let mut pattern_chars = right.chars();
160+
let first = pattern_chars.next();
161+
match first {
162+
Some('^') => {} // Start of string anchor is already added
163+
Some('*') => re_string.push_str(r"\*"),
164+
Some(char) => re_string.push(char),
165+
None => return Ok(0.into()),
166+
};
167+
168+
// Handle the rest of the input pattern.
169+
// Escape characters that should be handled literally within the pattern.
170+
let mut prev = first.unwrap_or_default();
171+
for curr in pattern_chars {
172+
match curr {
173+
'^' if prev != '\\' => re_string.push_str(r"\^"),
174+
char => re_string.push(char),
175+
}
176+
prev = curr;
177+
}
178+
155179
let re = Regex::with_options(
156180
&re_string,
157181
RegexOptions::REGEX_OPTION_NONE,

tests/by-util/test_expr.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,10 @@ fn test_length_mb() {
274274

275275
#[test]
276276
fn test_regex() {
277-
// FixME: [2022-12-19; rivy] test disabled as it currently fails due to 'oniguruma' bug (see GH:kkos/oniguruma/issues/279)
278-
// new_ucmd!()
279-
// .args(&["a^b", ":", "a^b"])
280-
// .succeeds()
281-
// .stdout_only("3\n");
277+
new_ucmd!()
278+
.args(&["a^b", ":", "a^b"])
279+
.succeeds()
280+
.stdout_only("3\n");
282281
new_ucmd!()
283282
.args(&["a^b", ":", "a\\^b"])
284283
.succeeds()
@@ -287,14 +286,39 @@ fn test_regex() {
287286
.args(&["a$b", ":", "a\\$b"])
288287
.succeeds()
289288
.stdout_only("3\n");
289+
new_ucmd!()
290+
.args(&["abc", ":", "^abc"])
291+
.succeeds()
292+
.stdout_only("3\n");
293+
new_ucmd!()
294+
.args(&["^abc", ":", "^^abc"])
295+
.succeeds()
296+
.stdout_only("4\n");
297+
new_ucmd!()
298+
.args(&["b^$ic", ":", "b^\\$ic"])
299+
.succeeds()
300+
.stdout_only("5\n");
301+
new_ucmd!()
302+
.args(&["^^^^^^^^^", ":", "^^^"])
303+
.succeeds()
304+
.stdout_only("2\n");
290305
new_ucmd!()
291306
.args(&["-5", ":", "-\\{0,1\\}[0-9]*$"])
292307
.succeeds()
293308
.stdout_only("2\n");
309+
new_ucmd!().args(&["", ":", ""]).fails().stdout_only("0\n");
310+
new_ucmd!()
311+
.args(&["abc", ":", ""])
312+
.fails()
313+
.stdout_only("0\n");
294314
new_ucmd!()
295315
.args(&["abc", ":", "bc"])
296316
.fails()
297317
.stdout_only("0\n");
318+
new_ucmd!()
319+
.args(&["^abc", ":", "^abc"])
320+
.fails()
321+
.stdout_only("0\n");
298322
}
299323

300324
#[test]
@@ -711,7 +735,6 @@ mod gnu_expr {
711735
.stdout_only("\n");
712736
}
713737

714-
#[ignore = "rust-onig bug, see https://github.com/rust-onig/rust-onig/issues/188"]
715738
#[test]
716739
fn test_bre10() {
717740
new_ucmd!()

0 commit comments

Comments
 (0)