From 5e327526b6e06fc606f4b6c707550d03722d2b52 Mon Sep 17 00:00:00 2001 From: mattsu Date: Fri, 26 Dec 2025 19:27:50 +0900 Subject: [PATCH 1/6] fix: allow duplicate -o options for same output file in sort Modify sort command to permit multiple -o/--output flags specifying the same file, matching GNU sort behavior. Previously, any multiple -o flags would error, but now only differing output files are rejected. Added test case for duplicate outputs. --- src/uu/sort/src/sort.rs | 12 +++++++----- tests/by-util/test_sort.rs | 10 ++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 8c27910dce8..0143b0db499 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1846,11 +1846,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), processed_args, 2)?; // Prevent -o/--output to be specified multiple times - if matches - .get_occurrences::(options::OUTPUT) - .is_some_and(|out| out.len() > 1) - { - return Err(SortError::MultipleOutputFiles.into()); + if let Some(outputs) = matches.get_many::(options::OUTPUT) { + let mut iter = outputs.into_iter(); + if let Some(first) = iter.next() { + if iter.any(|out| out != first) { + return Err(SortError::MultipleOutputFiles.into()); + } + } } settings.debug = matches.get_flag(options::DEBUG); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 0106d719fad..93af0729f89 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1450,6 +1450,16 @@ fn test_multiple_output_files() { .stderr_is("sort: multiple output files specified\n"); } +#[test] +// Test for GNU tests/sort/sort.pl "o3" +fn test_duplicate_output_files_allowed() { + new_ucmd!() + .args(&["-o", "foo", "-o", "foo"]) + .pipe_in("") + .succeeds() + .no_stderr(); +} + #[test] fn test_output_file_with_leading_dash() { let test_cases = [ From 3f16c720a336d01fe2886a3a2b4d12be020430f7 Mon Sep 17 00:00:00 2001 From: mattsu Date: Fri, 26 Dec 2025 19:46:44 +0900 Subject: [PATCH 2/6] fix(sort): fix case-insensitive sorting to order punctuation after letters Changed ascii_case_insensitive_cmp to fold characters to uppercase instead of lowercase, ensuring that in ASCII case-insensitive sorting, letters are ordered before punctuation. Added tests to verify the correct behavior. --- src/uu/sort/src/sort.rs | 8 ++++---- tests/by-util/test_sort.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 0143b0db499..c26c1af60d1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2622,13 +2622,13 @@ fn compare_by<'a>( /// untouched and we avoid locale-sensitive routines such as `strcasecmp`. fn ascii_case_insensitive_cmp(a: &[u8], b: &[u8]) -> Ordering { #[inline] - fn lower(byte: u8) -> u8 { - byte.to_ascii_lowercase() + fn fold(byte: u8) -> u8 { + byte.to_ascii_uppercase() } for (lhs, rhs) in a.iter().copied().zip(b.iter().copied()) { - let l = lower(lhs); - let r = lower(rhs); + let l = fold(lhs); + let r = fold(rhs); if l != r { return l.cmp(&r); } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 93af0729f89..0972cae0392 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -208,6 +208,24 @@ fn test_version_sort_stable() { .stdout_is("0.1\n0.02\n0.2\n0.002\n0.3\n"); } +#[test] +fn test_ignore_case_orders_punctuation_after_letters() { + new_ucmd!() + .arg("-f") + .pipe_in("A\na\n_\n") + .succeeds() + .stdout_is("A\na\n_\n"); +} + +#[test] +fn test_ignore_case_unique_orders_punctuation_after_letters() { + new_ucmd!() + .arg("-fu") + .pipe_in("a\n_\n") + .succeeds() + .stdout_is("a\n_\n"); +} + #[test] fn test_human_numeric_whitespace() { test_helper( From 8961468102fed8a71edd27f7aa7a53dacd39a7c4 Mon Sep 17 00:00:00 2001 From: mattsu <35655889+mattsu2020@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:18:48 +0900 Subject: [PATCH 3/6] Update src/uu/sort/src/sort.rs Co-authored-by: Sylvestre Ledru --- src/uu/sort/src/sort.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index c26c1af60d1..cab1e1afe8a 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1846,13 +1846,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), processed_args, 2)?; // Prevent -o/--output to be specified multiple times - if let Some(outputs) = matches.get_many::(options::OUTPUT) { - let mut iter = outputs.into_iter(); - if let Some(first) = iter.next() { - if iter.any(|out| out != first) { - return Err(SortError::MultipleOutputFiles.into()); - } - } + if let Some(mut outputs) = matches.get_many::(options::OUTPUT) { + if let Some(first) = outputs.next() { + if outputs.any(|out| out != first) { + return Err(SortError::MultipleOutputFiles.into()); + } + } + } } settings.debug = matches.get_flag(options::DEBUG); From f60a82c3270b7649b1549bb37092940686eb12e6 Mon Sep 17 00:00:00 2001 From: mattsu <35655889+mattsu2020@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:19:44 +0900 Subject: [PATCH 4/6] Update src/uu/sort/src/sort.rs Co-authored-by: Sylvestre Ledru --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index cab1e1afe8a..49d8a1074c0 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2618,7 +2618,7 @@ fn compare_by<'a>( } /// Compare two byte slices in ASCII case-insensitive order without allocating. -/// We lower each byte on the fly so that binary input (including `NUL`) stays +/// We upper each byte on the fly so that binary input (including `NUL`) stays /// untouched and we avoid locale-sensitive routines such as `strcasecmp`. fn ascii_case_insensitive_cmp(a: &[u8], b: &[u8]) -> Ordering { #[inline] From 39df2f1953333f9b80ca21f41fe4b884a6161cdf Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 27 Jan 2026 08:01:17 +0100 Subject: [PATCH 5/6] Remove unnecessary closing brace in sort.rs --- src/uu/sort/src/sort.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 49d8a1074c0..e43b851e8b5 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1852,7 +1852,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(SortError::MultipleOutputFiles.into()); } } - } } settings.debug = matches.get_flag(options::DEBUG); From 0bee20f8024241d07b4385abb46a9a03c378d94b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 27 Jan 2026 08:17:52 +0100 Subject: [PATCH 6/6] rustfmt --- src/uu/sort/src/sort.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index e43b851e8b5..2a887249ee5 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1847,11 +1847,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Prevent -o/--output to be specified multiple times if let Some(mut outputs) = matches.get_many::(options::OUTPUT) { - if let Some(first) = outputs.next() { - if outputs.any(|out| out != first) { - return Err(SortError::MultipleOutputFiles.into()); - } - } + if let Some(first) = outputs.next() { + if outputs.any(|out| out != first) { + return Err(SortError::MultipleOutputFiles.into()); + } + } } settings.debug = matches.get_flag(options::DEBUG);