diff --git a/src/uu/sort/src/custom_str_cmp.rs b/src/uu/sort/src/custom_str_cmp.rs index c8df6738944..d28195a943e 100644 --- a/src/uu/sort/src/custom_str_cmp.rs +++ b/src/uu/sort/src/custom_str_cmp.rs @@ -9,8 +9,13 @@ use std::cmp::Ordering; +// Newlines also act as blanks inside NUL-terminated records. +pub fn is_blank(c: u8) -> bool { + matches!(c, b' ' | b'\t' | b'\n') +} + fn filter_char(c: u8, ignore_non_printing: bool, ignore_non_dictionary: bool) -> bool { - if ignore_non_dictionary && !(c.is_ascii_alphanumeric() || c.is_ascii_whitespace()) { + if ignore_non_dictionary && !(c.is_ascii_alphanumeric() || is_blank(c)) { return false; } !(ignore_non_printing && (c.is_ascii_control() || !c.is_ascii())) diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 0d665acedf2..681ca2f789d 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -14,6 +14,7 @@ //! More specifically, exponent can be understood so that the original number is in `(1..10)*10^exponent`. //! From that follows the constraints of this algorithm: It is able to compare numbers in ±(1*10^[`i64::MIN`]..10*10^[`i64::MAX`]). +use crate::custom_str_cmp::is_blank; use std::{cmp::Ordering, ops::Range}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] @@ -63,7 +64,7 @@ impl NumInfo { let mut first_char = true; for (idx, &char) in num.iter().enumerate() { - if first_char && char.is_ascii_whitespace() { + if first_char && is_blank(char) { continue; } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index af39628f13f..b1975f0b6db 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -24,7 +24,7 @@ use bigdecimal::BigDecimal; use chunks::LineData; use clap::builder::ValueParser; use clap::{Arg, ArgAction, ArgMatches, Command}; -use custom_str_cmp::custom_str_cmp; +use custom_str_cmp::{custom_str_cmp, is_blank}; use ext_sort::ext_sort; use foldhash::fast::FoldHasher; use foldhash::{HashMap, SharedSeed}; @@ -934,7 +934,7 @@ fn tokenize_default( // pretend that there was whitespace in front of the line let mut previous_was_whitespace = true; for (idx, char) in line.iter().enumerate() { - let is_whitespace = char.is_ascii_whitespace(); + let is_whitespace = is_blank(*char); let treat_as_separator = if is_whitespace { if blank_thousands_sep && *char == b' ' { !is_blank_thousands_sep(line, idx, allow_unit_after_blank) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index bcc4bbc6c4c..c3c19452be0 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) ints (linux) Nofile dfgi abmon avril +// spell-checker:ignore (words) ints (linux) Nofile dfgi abmon avril naab #![allow(clippy::cast_possible_wrap)] @@ -4123,3 +4123,22 @@ sort: invalid suffix in --buffer-size argument '8zz' } // spell-checker:enable + +#[rstest::rstest] +#[case(&["-k2,3"], "aa\n0aa\r\n", "0aa\r\naa\n")] +#[case(&["-k2,3"], "aa\n0aa\x0c\n", "0aa\x0c\naa\n")] +#[case(&["-d"], "a\rb\naab\n", "aab\na\rb\n")] +#[case(&["-d"], "a\x0cb\naab\n", "aab\na\x0cb\n")] +#[case(&["-n"], "\r4\na\n", "\r4\na\n")] +#[case(&["-n"], "\x0c4\na\n", "\x0c4\na\n")] +#[case(&["-z", "-k2"], "b\n1\0a\n2\0", "b\n1\0a\n2\0")] +#[case(&["-z", "-d"], "a\nb\0aab\0", "a\nb\0aab\0")] +#[case(&["-z", "-n"], "\n4\0a\0", "a\0\n4\0")] +fn test_control_whitespace(#[case] args: &[&str], #[case] input: &str, #[case] expected: &str) { + new_ucmd!() + .env("LC_ALL", "C") + .args(args) + .pipe_in(input) + .succeeds() + .stdout_only(expected); +}