Skip to content

Commit b0390fe

Browse files
committed
expr: Handle $ at the beginning of the regex pattern
1 parent 4555e6f commit b0390fe

File tree

2 files changed

+40
-15
lines changed

2 files changed

+40
-15
lines changed

src/uu/expr/src/syntax_tree.rs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ impl StringOp {
161161
match first {
162162
Some('^') => {} // Start of string anchor is already added
163163
Some('*') => re_string.push_str(r"\*"),
164+
Some('$') if !is_end_of_expression(&pattern_chars) => re_string.push_str(r"\$"),
164165
Some('\\') if right.len() == 1 => return Err(ExprError::TrailingBackslash),
165166
Some(char) => re_string.push(char),
166167
None => return Ok(0.into()),
@@ -185,23 +186,12 @@ impl StringOp {
185186
_ => re_string.push_str(r"\^"),
186187
},
187188
'$' => {
188-
if let Some('\\') = pattern_chars.peek() {
189-
// The next character was checked to be a backslash
190-
let backslash = pattern_chars.next().unwrap_or_default();
191-
match pattern_chars.peek() {
192-
// End of a capturing group
193-
Some(')') => re_string.push('$'),
194-
// End of an alternative pattern
195-
Some('|') => re_string.push('$'),
196-
_ => re_string.push_str(r"\$"),
197-
}
198-
re_string.push(backslash);
199-
} else if (prev_is_escaped || prev != '\\')
200-
&& pattern_chars.peek().is_some()
201-
{
189+
if is_end_of_expression(&pattern_chars) {
190+
re_string.push(curr);
191+
} else if !curr_is_escaped {
202192
re_string.push_str(r"\$");
203193
} else {
204-
re_string.push('$');
194+
re_string.push(curr);
205195
}
206196
}
207197
'\\' if !curr_is_escaped && pattern_chars.peek().is_none() => {
@@ -247,6 +237,25 @@ impl StringOp {
247237
}
248238
}
249239

240+
/// Check if regex pattern character iterator is at the end of a regex expression or subexpression
241+
fn is_end_of_expression<I>(pattern_chars: &I) -> bool
242+
where
243+
I: Iterator<Item = char> + Clone,
244+
{
245+
let mut pattern_chars_clone = pattern_chars.clone();
246+
match pattern_chars_clone.next() {
247+
Some('\\') => {
248+
match pattern_chars_clone.next() {
249+
Some(')') // End of a capturing group
250+
| Some('|') => true, // End of an alternative pattern
251+
_ => false,
252+
}
253+
}
254+
None => true, // No characters left
255+
_ => false,
256+
}
257+
}
258+
250259
/// Check for errors in a supplied regular expression
251260
///
252261
/// GNU coreutils shows messages for invalid regular expressions

tests/by-util/test_expr.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,14 @@ fn test_regex() {
318318
.args(&["a$c", ":", "a$\\c"])
319319
.succeeds()
320320
.stdout_only("3\n");
321+
new_ucmd!()
322+
.args(&["$a", ":", "$a"])
323+
.succeeds()
324+
.stdout_only("2\n");
325+
new_ucmd!()
326+
.args(&["a", ":", "a$\\|b"])
327+
.succeeds()
328+
.stdout_only("1\n");
321329
new_ucmd!()
322330
.args(&["^^^^^^^^^", ":", "^^^"])
323331
.succeeds()
@@ -363,6 +371,14 @@ fn test_regex() {
363371
.args(&["abc", ":", "ab[^c]"])
364372
.fails()
365373
.stdout_only("0\n");
374+
new_ucmd!()
375+
.args(&["$", ":", "$"])
376+
.fails()
377+
.stdout_only("0\n");
378+
new_ucmd!()
379+
.args(&["a$", ":", "a$\\|b"])
380+
.fails()
381+
.stdout_only("0\n");
366382
}
367383

368384
#[test]

0 commit comments

Comments
 (0)