diff --git a/src/shell/commands/rm.rs b/src/shell/commands/rm.rs index ca4777f..1b7d881 100644 --- a/src/shell/commands/rm.rs +++ b/src/shell/commands/rm.rs @@ -63,7 +63,7 @@ async fn execute_remove(cwd: &Path, args: &[OsString]) -> Result<()> { remove_file_or_dir(&path, &flags).await }; if let Err(err) = result - && (err.kind() != ErrorKind::NotFound || !flags.force) + && !(flags.force && should_ignore_error_with_force(&err)) { bail!( "cannot remove '{}': {}", @@ -76,6 +76,24 @@ async fn execute_remove(cwd: &Path, args: &[OsString]) -> Result<()> { Ok(()) } +/// Check if an error should be silently ignored when -f (force) flag is used. +/// This includes: +/// - NotFound: file doesn't exist +/// - On Windows: InvalidFilename (os error 123) - happens when literal glob +/// patterns like "*.nonexistent" are passed (since * is invalid in Windows filenames) +fn should_ignore_error_with_force(err: &std::io::Error) -> bool { + if err.kind() == ErrorKind::NotFound { + return true; + } + // On Windows, glob characters like * are invalid in filenames. + // When a non-matching glob is passed literally, Windows returns error 123. + #[cfg(windows)] + if err.raw_os_error() == Some(123) { + return true; + } + false +} + async fn remove_file_or_dir( path: &Path, flags: &RmFlags<'_>, diff --git a/tests/integration_test.rs b/tests/integration_test.rs index d48304c..362c86c 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1067,6 +1067,28 @@ async fn rm() { .assert_exit_code(1) .run() .await; + + // rm -f should ignore non-existent files + TestBuilder::new() + .command("rm -f nonexistent.txt") + .assert_exit_code(0) + .run() + .await; + + // rm -rf should ignore non-existent directories + TestBuilder::new() + .command("rm -rf nonexistent_dir") + .assert_exit_code(0) + .run() + .await; + + // rm -rf with glob pattern that matches nothing should succeed + // (when failglob is disabled, the pattern is passed literally to rm) + TestBuilder::new() + .command("shopt -u failglob && rm -rf *.nonexistent") + .assert_exit_code(0) + .run() + .await; } // Basic integration tests as there are unit tests in the commands