diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index adb56bce5..295bc4ffe 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -708,9 +708,16 @@ where let filtered = buf.iter().filter_map(|&c| translator.translate(c)); output_buf.extend(filtered); - output - .write_all(&output_buf) - .map_err_context(|| get_message("tr-error-write-error"))?; + if let Err(err) = output.write_all(&output_buf) { + // Treat broken pipe as a successful termination, which is the + // expected behavior when stdout is a pipeline and the downstream + // process terminates. + if err.kind() == std::io::ErrorKind::BrokenPipe { + break; + } else { + return Err(err.map_err_context(|| get_message("tr-error-write-error"))); + } + } buf.clear(); output_buf.clear(); diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 1ae9ddadf..b446fa3a9 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -13,7 +13,7 @@ use operation::{ }; use std::collections::HashMap; use std::ffi::OsString; -use std::io::{BufWriter, Write, stdin, stdout}; +use std::io::{BufWriter, ErrorKind, Write, stdin, stdout}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::is_stdin_directory; @@ -157,9 +157,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } - buffered_stdout - .flush() - .map_err_context(|| get_message("tr-error-write-error"))?; + // Handle broken pipe errors gracefully during flush. + match buffered_stdout.flush() { + Ok(()) => {} + Err(err) if err.kind() == ErrorKind::BrokenPipe => {} + Err(err) => return Err(err.map_err_context(|| get_message("tr-error-write-error"))), + } Ok(()) } diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index d7ecdec1f..e76fd84d7 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1554,3 +1554,13 @@ fn test_failed_write_is_reported() { .fails() .stderr_is("tr: write error: No space left on device\n"); } + +#[test] +fn test_broken_pipe_no_error() { + new_ucmd!() + .args(&["e", "a"]) + .pipe_in("hello".repeat(100)) + .run_stdout_starts_with(b"") + .success() + .stderr_is(""); +}