Skip to content
Closed
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
9 changes: 9 additions & 0 deletions src/sed/fast_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,15 @@ impl OutputBuffer {
)))
}

/// Schedule the specified text for output unchanged, after any
/// deferred newline.
pub fn write_raw(&mut self, bytes: &[u8]) -> io::Result<()> {
self.flush_pending_newline()?;
#[cfg(unix)]
self.flush_mmap(WriteRange::Complete)?;
self.out.write_all(bytes)
}

/// Copy the specified file to the output.
pub fn copy_file(&mut self, path: &PathBuf) -> io::Result<()> {
// Flush mmap writes, if any.
Expand Down
8 changes: 3 additions & 5 deletions src/sed/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ fn flush_appends(output: &mut OutputBuffer, context: &mut ProcessingContext) ->
for elem in &context.append_elements {
match elem {
AppendElement::Text(text) => {
output.write_bytes(text.as_ref())?;
output.write_raw(text.as_ref())?;
}
AppendElement::Path(path) => {
output.copy_file(path)?;
Expand Down Expand Up @@ -567,9 +567,7 @@ fn list(
) -> UResult<()> {
// Special case for an empty pattern space
if line.is_empty() {
if line.is_newline_terminated() {
output.write_str("$\n")?;
}
output.write_str("$\n")?;
return Ok(());
}

Expand Down Expand Up @@ -737,7 +735,7 @@ fn process_file(
}
CommandData::Text(cmd_bytes) => {
let shell_out = shell_stdout(cmd_bytes.to_vec(), &command, context)?;
output.write_bytes(&shell_out)?;
output.write_raw(&shell_out)?;
}
_ => panic!("invalid 'e' command data"),
},
Expand Down
42 changes: 42 additions & 0 deletions tests/by-util/test_sed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3090,3 +3090,45 @@ fn test_posix_reject_flags() {
.code_is(1)
.stderr_is("sed: <script argument 1>:1:7: error: unknown option to 's'\n");
}

/// `R` copies a line lacking a newline unchanged, as GNU sed does.
#[test]
fn read_one_line_without_newline_is_copied_unchanged() -> std::io::Result<()> {
let temp = NamedTempFile::new()?;
fs::write(temp.path(), "x")?;
new_ucmd!()
.arg(format!("R {}", temp.path().display()))
.pipe_in("a\nb\n")
.succeeds()
.stdout_is("a\nxb\n");
Ok(())
}

/// The output of the `e` command is copied unchanged, as GNU sed does.
#[cfg(unix)]
#[test]
fn execute_command_output_is_copied_unchanged() {
new_ucmd!()
.arg("1e printf hi")
.pipe_in("a\nb\n")
.succeeds()
.stdout_is("hia\nb\n");
// A line that lacks its newline is ended before the output, even when
// there is none.
new_ucmd!()
.args(&["-n", "p;e true"])
.pipe_in("a")
.succeeds()
.stdout_is("a\n");
}

/// `l` shows an empty pattern space as `$`, even when the line lacked a
/// newline, as GNU sed does.
#[test]
fn list_empty_line_without_newline() {
new_ucmd!()
.args(&["-n", "s/a//;l"])
.pipe_in("a")
.succeeds()
.stdout_is("$\n");
}
Loading