diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 681ca2f789d..fe9a16d6479 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -31,6 +31,8 @@ pub struct NumInfo { #[derive(Debug, PartialEq, Eq, Clone)] pub struct NumInfoParseSettings { pub accept_si_units: bool, + /// Also accept lowercase SI units, as with `-f`. + pub fold_si_units: bool, pub thousands_separator: Option, pub decimal_pt: Option, } @@ -39,6 +41,7 @@ impl Default for NumInfoParseSettings { fn default() -> Self { Self { accept_si_units: false, + fold_si_units: false, thousands_separator: None, decimal_pt: Some(b'.'), } @@ -81,8 +84,13 @@ impl NumInfo { if Self::is_invalid_char(char, &mut had_decimal_pt, parse_settings) { return if let Some(start) = start { + let unit = if parse_settings.fold_si_units { + char.to_ascii_uppercase() + } else { + char + }; let has_si_unit = - parse_settings.accept_si_units && b"kKMGTPEZYRQ".contains(&char); + parse_settings.accept_si_units && b"kKMGTPEZYRQ".contains(&unit); ( Self { exponent, sign }, start..if has_si_unit { idx + 1 } else { idx }, @@ -165,14 +173,21 @@ impl NumInfo { } } +/// A lowercase unit other than `k` is only part of the range with `-f`, so it is +/// read as the uppercase one. fn get_unit(unit: Option) -> u8 { match unit { - Some(b'k') => 1, - Some(u) => b"KMGTPEZYRQ" - .iter() - .position(|&c| c == u) - .map_or(0, |pos| (pos + 1) as u8), - None => 0, + Some(b'k' | b'K') => 1, + Some(b'M' | b'm') => 2, + Some(b'G' | b'g') => 3, + Some(b'T' | b't') => 4, + Some(b'P' | b'p') => 5, + Some(b'E' | b'e') => 6, + Some(b'Z' | b'z') => 7, + Some(b'Y' | b'y') => 8, + Some(b'R' | b'r') => 9, + Some(b'Q' | b'q') => 10, + _ => 0, } } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 94edeaa8ca2..992267333c4 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -336,9 +336,10 @@ impl Default for NumericLocaleSettings { } impl NumericLocaleSettings { - fn num_info_settings(self, accept_si_units: bool) -> NumInfoParseSettings { + fn num_info_settings(self, accept_si_units: bool, fold_si_units: bool) -> NumInfoParseSettings { NumInfoParseSettings { accept_si_units, + fold_si_units, thousands_separator: self.thousands_sep, decimal_pt: self.decimal_pt, } @@ -788,9 +789,10 @@ impl<'a> Line<'a> { match selector.settings.mode { SortMode::Numeric | SortMode::HumanNumeric => { // find out which range is used for numeric comparisons + // GNU marks a lowercase unit as not part of the key, even with -f. let mut parse_settings = settings .numeric_locale - .num_info_settings(selector.settings.mode == SortMode::HumanNumeric); + .num_info_settings(selector.settings.mode == SortMode::HumanNumeric, false); // Debug annotations should ignore thousands separators to match GNU output. parse_settings.thousands_separator = None; let (_, num_range) = @@ -1369,7 +1371,10 @@ impl FieldSelector { // Parse NumInfo for this number. let (info, num_range) = NumInfo::parse( range_str, - &numeric_locale.num_info_settings(self.settings.mode == SortMode::HumanNumeric), + &numeric_locale.num_info_settings( + self.settings.mode == SortMode::HumanNumeric, + self.settings.ignore_case, + ), ); // Shorten the range to what we need to pass to numeric_str_cmp later. range_str = &range_str[num_range]; diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 23dd109dd12..daead3a1baa 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -592,6 +592,44 @@ fn test_human_block_sizes2() { } } +#[test] +fn test_human_numeric_ignore_case() { + // With -f, lowercase units other than k are read as the uppercase ones + for args in [&["-h", "-f"][..], &["-hf"], &["-k1,1hf"]] { + new_ucmd!() + .args(args) + .pipe_in("2m\n1e\n3g\n1K\n-1m\n") + .succeeds() + .stdout_only("-1m\n1K\n2m\n3g\n1e\n"); + } + // Without -f (a key with its own options does not inherit the global + // one) they are not units + for args in [&["-h"][..], &["-f", "-k1h"]] { + new_ucmd!() + .args(args) + .pipe_in("2m\n1e\n3g\n1K\n-1m\n") + .succeeds() + .stdout_only("-1m\n1e\n2m\n3g\n1K\n"); + } +} + +#[test] +fn test_human_numeric_ignore_case_debug() { + // GNU sorts by the folded unit but does not mark a lowercase unit as part of the key + new_ucmd!() + .env("LC_ALL", "C") + .args(&["--debug", "-h", "-f"]) + .pipe_in("2m\n1e\n3g\n1K\n-1m\n") + .succeeds() + .stdout_is(concat!( + "-1m\n__\n___\n", + "1K\n__\n__\n", + "2m\n_\n__\n", + "3g\n_\n__\n", + "1e\n_\n__\n", + )); +} + #[test] fn test_human_numeric_zero_stable() { let input = "0M\n0K\n-0K\n-P\n-0M\n";