From 238b27eddf8b4ec691bf912d12ad6e0367fa2c08 Mon Sep 17 00:00:00 2001 From: mattsu <35655889+mattsu2020@users.noreply.github.com> Date: Mon, 19 Jan 2026 02:57:37 +0900 Subject: [PATCH] wc:Ensure the output order of stdout and stderror remains unchanged. (#9905) --------- Co-authored-by: Sylvestre Ledru --- src/uu/wc/src/wc.rs | 18 ++++++++++++------ tests/by-util/test_wc.rs | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 1f4b67c20..4ae07fe2b 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -950,12 +950,13 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { } }; - let word_count = match word_count_from_input(&input, settings) { - CountResult::Success(word_count) => word_count, - CountResult::Interrupted(word_count, err) => { - show!(err.map_err_context(|| input.path_display())); - word_count - } + // Store any I/O error from reading to print AFTER stats (matches GNU wc behavior) + let (word_count, deferred_error) = match word_count_from_input(&input, settings) { + CountResult::Success(word_count) => (word_count, None), + CountResult::Interrupted(word_count, err) => ( + word_count, + Some(err.map_err_context(|| input.path_display())), + ), CountResult::Failure(err) => { show!(err.map_err_context(|| input.path_display())); continue; @@ -970,6 +971,11 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { show!(err.map_err_context(|| translate!("wc-error-failed-to-print-result", "title" => title.to_string_lossy()))); } } + // Print deferred error after stats to match GNU wc output order + if let Some(err) = deferred_error { + let _ = io::stdout().flush(); + show!(err); + } } if settings.total_when.is_total_row_visible(num_inputs) { diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index d1266e09d..be2374283 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -8,7 +8,7 @@ use uutests::at_and_ucmd; use uutests::new_ucmd; use uutests::util::vec_of_size; -// spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword weirdchars +// spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword weirdchars ioerrdir #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails_with_code(1); @@ -449,6 +449,23 @@ fn test_read_from_directory_error() { .stdout_is(STDOUT); } +#[cfg(unix)] +#[test] +fn test_read_error_order_with_stderr_to_stdout() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("ioerrdir"); + + let expected = format!( + "{:>7} {:>7} {:>7} ioerrdir\nwc: ioerrdir: Is a directory\n", + 0, 0, 0 + ); + + ucmd.arg("ioerrdir") + .stderr_to_stdout() + .fails() + .stdout_only(expected); +} + /// Test that getting counts from nonexistent file is an error. #[test] fn test_read_from_nonexistent_file() {