From 33c5ce012978f54d438358f9887ceb65ba35439a Mon Sep 17 00:00:00 2001 From: Logic769 Date: Fri, 30 Oct 2026 04:53:41 +0800 Subject: [PATCH 1/2] rm: keep missing-operand error after -f with -i/-I --- src/uu/rm/src/rm.rs | 12 ++++++------ tests/by-util/test_rm.rs | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 2afa1093691..c392e45bc0d 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -248,12 +248,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let force_flag = matches.get_flag(OPT_FORCE); - if files.is_empty() && !force_flag { - // Still check by hand and not use clap - // Because "rm -f" is a thing - return Err(RmError::MissingOperand.into()); - } - // If -f(--force) is before any -i (or variants) we want prompts else no prompts let force_prompt_never = force_flag && { let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); @@ -265,6 +259,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }) }; + if files.is_empty() && !force_prompt_never { + // Still check by hand and not use clap. + // Because "rm -f" is a thing, but a later -i/-I/--interactive cancels it. + return Err(RmError::MissingOperand.into()); + } + let preserve_root = !matches.get_flag(OPT_NO_PRESERVE_ROOT); let preserve_root_all = matches .get_one::(OPT_PRESERVE_ROOT) diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 2a41460b1cb..17c9d76b989 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -503,6 +503,31 @@ fn test_no_operand() { new_ucmd!().fails().usage_error("missing operand"); } +#[test] +fn test_force_then_interactive_no_operand() { + // A later -i cancels -f, so the missing operand must be reported. + new_ucmd!() + .args(&["-f", "-i"]) + .fails() + .usage_error("missing operand"); +} + +#[test] +fn test_force_then_prompt_once_no_operand() { + new_ucmd!() + .args(&["--force", "-I"]) + .fails() + .usage_error("missing operand"); +} + +#[test] +fn test_force_then_interactive_always_no_operand() { + new_ucmd!() + .args(&["-f", "--interactive=always"]) + .fails() + .usage_error("missing operand"); +} + #[test] fn test_verbose_slash() { let (at, mut ucmd) = at_and_ucmd!(); From 49ff7d934464832246e48f0acfcf4b72fc6079f9 Mon Sep 17 00:00:00 2001 From: Logic769 Date: Tue, 3 Nov 2026 07:54:41 +0800 Subject: [PATCH 2/2] rm: ignore --interactive=never when checking -f --- src/uu/rm/src/rm.rs | 16 ++++++++++------ tests/by-util/test_rm.rs | 38 ++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index c392e45bc0d..cda217f1158 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -251,12 +251,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // If -f(--force) is before any -i (or variants) we want prompts else no prompts let force_prompt_never = force_flag && { let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); - ![OPT_PROMPT_ALWAYS, OPT_PROMPT_ONCE, OPT_INTERACTIVE] - .iter() - .any(|flag| { - matches.value_source(flag) == Some(ValueSource::CommandLine) - && matches.index_of(flag).unwrap_or(0) > force_index - }) + let is_later = |flag: &str| { + matches.value_source(flag) == Some(ValueSource::CommandLine) + && matches.index_of(flag).unwrap_or(0) > force_index + }; + // `--interactive=never` does not cancel an earlier `-f`. + let interactive_cancels_force = is_later(OPT_INTERACTIVE) + && matches + .get_one::(OPT_INTERACTIVE) + .is_some_and(|mode| mode != "never"); + !is_later(OPT_PROMPT_ALWAYS) && !is_later(OPT_PROMPT_ONCE) && !interactive_cancels_force }; if files.is_empty() && !force_prompt_never { diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 17c9d76b989..3acad56c46e 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -495,7 +495,9 @@ fn test_invalid_symlink() { #[test] fn test_force_no_operand() { - new_ucmd!().arg("-f").succeeds().no_stderr(); + for args in [&["-f"][..], &["-f", "--interactive=never"], &["-i", "-f"]] { + new_ucmd!().args(args).succeeds().no_stderr(); + } } #[test] @@ -505,27 +507,19 @@ fn test_no_operand() { #[test] fn test_force_then_interactive_no_operand() { - // A later -i cancels -f, so the missing operand must be reported. - new_ucmd!() - .args(&["-f", "-i"]) - .fails() - .usage_error("missing operand"); -} - -#[test] -fn test_force_then_prompt_once_no_operand() { - new_ucmd!() - .args(&["--force", "-I"]) - .fails() - .usage_error("missing operand"); -} - -#[test] -fn test_force_then_interactive_always_no_operand() { - new_ucmd!() - .args(&["-f", "--interactive=always"]) - .fails() - .usage_error("missing operand"); + // A later -i, -I or --interactive=always/once cancels -f, + // so the missing operand must be reported. + for args in [ + &["-f", "-i"][..], + &["--force", "-I"], + &["-f", "--interactive=always"], + &["-f", "--interactive=once"], + ] { + new_ucmd!() + .args(args) + .fails() + .usage_error("missing operand"); + } } #[test]