Skip to content
Open
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
1 change: 1 addition & 0 deletions src/sed/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ pub struct Substitution {
pub regex: Option<Regex>, // Regular expression
pub replacement: ReplacementTemplate, // Specified broken-down replacement
pub occurrence: usize, // Which occurrence to substitute
pub global: bool, // True if 'g' flag: also every later one
pub print_flag: bool, // True if 'p' flag
pub p_before_e: bool, // True if 'p' appears before 'e'
pub ignore_case: bool, // True if 'I' flag
Expand Down
35 changes: 24 additions & 11 deletions src/sed/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -958,9 +958,10 @@ pub fn compile_subst_flags(
posix: bool,
sandbox: bool,
) -> UResult<()> {
let mut seen_g_or_n = false;
let mut seen_number = false;

subst.occurrence = 1; // default
subst.global = false;
subst.print_flag = false;
subst.p_before_e = false;
subst.ignore_case = false;
Expand All @@ -976,15 +977,14 @@ pub fn compile_subst_flags(

match line.current() {
'g' => {
if seen_g_or_n {
if subst.global {
return compilation_error(
lines,
line,
"multiple 'g' or numeric flags in substitute command",
);
}
seen_g_or_n = true;
subst.occurrence = 0;
subst.global = true;
line.advance();
}

Expand Down Expand Up @@ -1024,7 +1024,7 @@ pub fn compile_subst_flags(
}

_c @ '1'..='9' => {
if seen_g_or_n {
if seen_number {
return compilation_error(
lines,
line,
Expand All @@ -1049,7 +1049,7 @@ pub fn compile_subst_flags(
}

subst.occurrence = number;
seen_g_or_n = true;
seen_number = true;
}

'w' => {
Expand Down Expand Up @@ -2601,7 +2601,8 @@ mod tests {
let mut subst = Substitution::default();

compile_subst_flags(&lines, &mut chars, &mut subst, false, false).unwrap();
assert_eq!(subst.occurrence, 0); // 'g' means all occurrences
assert_eq!(subst.occurrence, 1);
assert!(subst.global);
}

#[test]
Expand Down Expand Up @@ -2659,8 +2660,20 @@ mod tests {
}

#[test]
fn test_compile_subst_flag_g_and_number_should_fail() {
let (lines, mut chars) = make_providers("g3");
fn test_compile_subst_flag_g_and_number() {
for flags in ["g3", "3g"] {
let (lines, mut chars) = make_providers(flags);
let mut subst = Substitution::default();

compile_subst_flags(&lines, &mut chars, &mut subst, false, false).unwrap();
assert_eq!(subst.occurrence, 3);
assert!(subst.global);
}
}

#[test]
fn test_compile_subst_flag_multiple_g_should_fail() {
let (lines, mut chars) = make_providers("gpg");
let mut subst = Substitution::default();

let err = compile_subst_flags(&lines, &mut chars, &mut subst, false, false).unwrap_err();
Expand All @@ -2671,8 +2684,8 @@ mod tests {
}

#[test]
fn test_compile_subst_flag_number_and_g_should_fail() {
let (lines, mut chars) = make_providers("2g");
fn test_compile_subst_flag_multiple_numbers_should_fail() {
let (lines, mut chars) = make_providers("2g3");
let mut subst = Substitution::default();

let err = compile_subst_flags(&lines, &mut chars, &mut subst, false, false).unwrap_err();
Expand Down
12 changes: 6 additions & 6 deletions src/sed/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ fn substitute(
// The following let block allows a common input_runtime_error to be
// called once in all cases, and most importantly, to finish the regex
// mutable borrowing of context, so as to reuse context in the error call.
let subst_result = match (sub.occurrence, sub.replacement.max_group_number) {
(1, 0) => {
let subst_result = match (sub.occurrence, sub.global, sub.replacement.max_group_number) {
(1, false, 0) => {
// Example: s/foo/bar/: find() is enough.
match regex.find(pattern) {
Err(e) => Err(e),
Expand All @@ -323,7 +323,7 @@ fn substitute(
}
}

(1, _) => {
(1, false, _) => {
// Example: s/\(.\)\(.\)/\2\1/: captures() is enough.
match regex.captures(pattern) {
Err(e) => Err(e),
Expand All @@ -341,7 +341,7 @@ fn substitute(
}
}

(_, _) => {
(_, _, _) => {
// Example: s/(.)(.)/\2\1/3: captures_iter() is needed.
// Iterate over multiple captures of the RE in the pattern.
'captures: {
Expand All @@ -357,7 +357,7 @@ fn substitute(
// Always write the unmatched text before this match.
result.extend_from_slice(&text[last_end..m.start()]);

if sub.occurrence == 0 || count == sub.occurrence {
if count == sub.occurrence || (sub.global && count > sub.occurrence) {
let replacement = sub.replacement.apply_captures(command, &caps)?;
result.extend_from_slice(&replacement);
replaced = true;
Expand All @@ -370,7 +370,7 @@ fn substitute(

// Early exit if only a specific occurrence,
// (likely 1) needed replacing.
if count == sub.occurrence {
if !sub.global && count == sub.occurrence {
break 'captures Ok(());
}
}
Expand Down
16 changes: 16 additions & 0 deletions tests/by-util/test_sed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,22 @@ fn subst_multiline_flag_matches_embedded_line_end() {
.stdout_is("foX\nbaX\n");
}

#[test]
fn subst_number_and_g_replace_from_that_match_on() {
for flags in ["2g", "g2"] {
new_ucmd!()
.arg(format!("s/a/b/{flags}"))
.pipe_in("aaaa\n")
.succeeds()
.stdout_is("abbb\n");
}
new_ucmd!()
.arg(r"s/\(a\)/<\1>/3g")
.pipe_in("aaaa\n")
.succeeds()
.stdout_is("aa<a><a>\n");
}

// Check appropriate selection and behavior of fast_Regex matcher
// Literal matcher
check_output!(subst_literal_start, ["-e", r"s/^l1/L1/", LINES1]);
Expand Down
Loading