diff --git a/src/sed/command.rs b/src/sed/command.rs index fbf335ee..1af1e630 100644 --- a/src/sed/command.rs +++ b/src/sed/command.rs @@ -392,8 +392,17 @@ pub enum SpaceFlag { pub struct InputAction { /// Next command to execute (rather than commands from start) pub next_command: Option>>, - /// Data to prepend to the read contents - pub prepend: Vec, + /// How the read line becomes the pattern space + pub next_line: NextLine, +} + +#[derive(Debug, Clone)] +/// How a line read by `n` or `N` becomes the pattern space +pub enum NextLine { + /// Replace the pattern space with the line (`n`) + Replace, + /// Append the line, after a newline, to these contents (`N`) + Append(Vec), } #[cfg(test)] diff --git a/src/sed/processor.rs b/src/sed/processor.rs index 3f0ebb8b..298a974e 100644 --- a/src/sed/processor.rs +++ b/src/sed/processor.rs @@ -9,8 +9,8 @@ // file that was distributed with this source code. use crate::sed::command::{ - Address, AppendElement, CharacterMode, Command, CommandData, InputAction, ProcessingContext, - Transliteration, + Address, AppendElement, CharacterMode, Command, CommandData, InputAction, NextLine, + ProcessingContext, Transliteration, }; use crate::sed::delimited_parser::os_string_from_bytes; use crate::sed::error_handling::{ScriptLocation, input_runtime_error}; @@ -654,12 +654,13 @@ fn process_file( // Set the script command from which to start. let mut current: Option>> = if let Some(action) = context.input_action.take() { - // Continue processing the `N` command. - let mut combined_lines = action.prepend; - combined_lines.push(b'\n'); - combined_lines.extend_from_slice(pattern.as_bytes()); + // Continue processing the `N` or `n` command. + if let NextLine::Append(mut combined_lines) = action.next_line { + combined_lines.push(b'\n'); + combined_lines.extend_from_slice(pattern.as_bytes()); - pattern.set_to_bytes(combined_lines, pattern.is_newline_terminated()); + pattern.set_to_bytes(combined_lines, pattern.is_newline_terminated()); + } action.next_command } else { // Start from the script top. @@ -779,7 +780,17 @@ fn process_file( list(output, &pattern, width, &command.location, context)?; } 'n' => { - break; + // Print the pattern space and continue with the next + // command once the next line is read, as `N` does. + if !context.quiet { + write_chunk(output, context, &pattern)?; + } + flush_appends(output, context)?; + context.input_action = Some(InputAction { + next_command: command.next.clone(), + next_line: NextLine::Replace, + }); + continue 'lines; } 'N' => { flush_appends(output, context)?; @@ -789,7 +800,7 @@ fn process_file( // to perform when the next line is read. context.input_action = Some(InputAction { next_command: command.next.clone(), - prepend: pattern.as_bytes().to_vec(), + next_line: NextLine::Append(pattern.as_bytes().to_vec()), }); continue 'lines; } @@ -939,13 +950,13 @@ fn process_file( } } - // Handle any N command remains. - // Take it even with -n, so that it does not carry over to the next file. + // Handle any n or N command remains. + // Take them even with -n, so that they do not carry over to the next file. if context.separate && let Some(action) = context.input_action.take() + && let NextLine::Append(mut pending) = action.next_line && !context.quiet { - let mut pending = action.prepend; pending.push(b'\n'); output.write_bytes(&pending)?; if context.unbuffered { @@ -1005,8 +1016,8 @@ pub fn process_all_files( && !context.separate && !context.quiet && let Some(action) = context.input_action.take() + && let NextLine::Append(mut pending) = action.next_line { - let mut pending = action.prepend; pending.push(b'\n'); output.write_bytes(&pending)?; } diff --git a/tests/by-util/test_sed.rs b/tests/by-util/test_sed.rs index add24b83..5597d2fe 100644 --- a/tests/by-util/test_sed.rs +++ b/tests/by-util/test_sed.rs @@ -1376,6 +1376,40 @@ check_output!(pattern_quit_2, [r"5q", LINES1, LINES2]); check_output!(pattern_re_reuse, ["-n", r"/_1/p;//p", LINES1]); check_output!(pattern_subst_re_reuse, ["-n", r"/_1/p;s//-N/p", LINES1]); +#[test] +fn test_next_continues_with_following_command() { + new_ucmd!() + .args(&["n;d"]) + .pipe_in("1\n2\n3\n4\n") + .succeeds() + .stdout_is("1\n3\n"); + new_ucmd!() + .args(&["-n", "-e", "a A", "-e", "n;p"]) + .pipe_in("a\nb\n") + .succeeds() + .stdout_is("A\nb\n"); + // At the end of input, `n` ends processing. + new_ucmd!() + .args(&["n;q5"]) + .pipe_in("1\n") + .succeeds() + .stdout_is("1\n"); +} + +#[test] +fn test_next_reads_across_files_unless_separate() { + new_ucmd!() + .args(&["n;s/^/X/", "-", "input/two-lines.txt"]) + .pipe_in("a\n") + .succeeds() + .stdout_is("a\nXline one\nline two\n"); + new_ucmd!() + .args(&["-s", "n;s/^/X/", "-", "input/two-lines.txt"]) + .pipe_in("a\n") + .succeeds() + .stdout_is("a\nline one\nXline two\n"); +} + #[test] fn test_quit_exit_code() { new_ucmd!() @@ -2544,6 +2578,14 @@ fn in_place_edit_separate() -> std::io::Result<()> { "a1+a2\na3\n", "b1+b2\nb3\n", ), + // n on the last line of a file does not read the first line of the next. + ( + &["-i", "-e", "n;s/^/X/"], + a, + b, + "a1\nXa2\na3\n", + "b1\nXb2\nb3\n", + ), // Lines collected in the hold space stay in their own file. ( &["-i", "-e", r"H;$!d;x;s/\n/,/g"],