From 5835de8dcf062e8a832fe6ddd10949e140f06f1e Mon Sep 17 00:00:00 2001 From: m0g3r <87276771+m0g3r@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:29:28 +0200 Subject: [PATCH] pr: accept -e at the end of a short flag cluster `-e` takes an optional attached argument, which clap cannot express, so `recreate_arguments` fills in the default. It only recognised an argument starting with `-e`, so a cluster such as `-tre` reached clap as a bare `-e` and was rejected with "a value is required for --expand-tabs". Match a cluster of value-less short flags ending in `e` instead, and append the default rather than replacing the whole argument. Options that take a value of their own are excluded so `-se` still means `-s e`. Fixes #13895 Co-authored-by: Claude Opus 5 --- src/uu/pr/src/pr.rs | 15 ++++++++------- tests/by-util/test_pr.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index ec154d6b027..db753e12c3d 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -427,7 +427,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { fn recreate_arguments(args: &[String]) -> Vec { let num_regex = Regex::new(r"^[^-]\d*$").unwrap(); let n_regex = Regex::new(r"^-n\s*$").unwrap(); - let e_regex = Regex::new(r"^-e").unwrap(); + // `-e` ends a cluster of short flags that take no value of their own, as in `-tre`. + // Options that do take a value are excluded so that `-se` keeps meaning `-s e`. + let e_regex = Regex::new(r"^-[dtTrFfabmJ]*e$").unwrap(); let mut arguments = args.to_owned(); let num_option = args .iter() @@ -442,16 +444,15 @@ fn recreate_arguments(args: &[String]) -> Vec { arguments.insert(pos + 2, could_be_file); } - // To ensure not to accidentally delete the next argument after a short flag for -e we insert - // the default values for the -e flag is '-e' is present without direct arguments. + // `-e` takes an optional attached argument, which clap cannot express, so it is filled in + // here. Without it clap would report a missing value for `-tre`, or swallow the following + // argument, usually a file name, for a bare `-e`. let expand_tabs_option = arguments .iter() .take_while(|arg| arg.as_str() != "--") .find_position(|x| e_regex.is_match(x.trim())); - if let Some((pos, value)) = expand_tabs_option - && value.trim().len() <= 2 - { - arguments[pos] = "-e\t8".to_string(); + if let Some((pos, value)) = expand_tabs_option { + arguments[pos] = format!("{}\t8", value.trim()); } // Remove only whole-token legacy operands before clap parsing. diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index 9df12978580..88f2f8da6b4 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -945,6 +945,34 @@ fn test_simple_expand_tab() { .stdout_matches(&output_regex); } +#[test] +fn test_expand_tab_at_end_of_short_flag_cluster() { + // `-e` closing a cluster of value-less short flags carries no attached argument, so it has + // to fall back to the defaults rather than report a missing value. + for (arg, expected) in [ + ("-tre", "oi\n"), + ("-tre8", "oi\n"), + ("-tfre", "oi\n\x0c"), + ("-tfre8", "oi\n\x0c"), + ] { + new_ucmd!() + .arg(arg) + .pipe_in("oi\n") + .succeeds() + .stdout_only(expected); + } +} + +#[test] +fn test_expand_tab_does_not_consume_following_operand() { + // A bare `-e` must leave the file operand alone. + new_ucmd!() + .args(&["-t", "-e"]) + .pipe_in("a\tb\n") + .succeeds() + .stdout_only("a b\n"); +} + #[test] fn test_simple_expand_tab_with_digit_argument() { let whitespace = " ".repeat(50);