diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index 1a2cc68a..27e630fd 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -65,6 +65,7 @@ use ls::Ls; use std::{ error::Error, fs::{File, Metadata}, + io::{stderr, Write}, path::{Path, PathBuf}, str::FromStr, time::SystemTime, @@ -534,6 +535,20 @@ fn build_matcher_tree( return Err(From::from(format!("missing argument to {}", args[i]))); } i += 1; + // GNU find warns when a -name/-iname pattern contains a directory + // separator: -name matches basenames only, so such a pattern can + // never match and the user likely meant -wholename. See issue #783. + // A pattern made up only of '/' (e.g. `-name /`) is left alone: `/` + // is a valid basename for the root entry, so warning would be a + // false alarm (GNU itself miscategorises this — bug #62227). + if args[i].contains('/') && args[i].chars().any(|c| c != '/') { + writeln!( + &mut stderr(), + "find: warning: '{}' matches against basenames only, but the given pattern contains a directory separator ('/'), thus the expression will evaluate to false all the time. Did you mean '-wholename'?", + args[i - 1] + ) + .unwrap(); + } Some(NameMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box()) } "-path" | "-ipath" | "-wholename" | "-iwholename" => { @@ -541,6 +556,19 @@ fn build_matcher_tree( return Err(From::from(format!("missing argument to {}", args[i]))); } i += 1; + // GNU find warns when a -path/-wholename pattern ends with '/': + // a trailing separator can never match a real path. See issue #783. + // As with -name, a pattern of only '/' is exempt: `-path /` can + // legitimately match the root entry. + if args[i].ends_with('/') && args[i].chars().any(|c| c != '/') { + writeln!( + &mut stderr(), + "find: warning: {} {} will not match anything because it ends with /.", + args[i - 1], + args[i] + ) + .unwrap(); + } Some(PathMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box()) } "-readable" => Some(AccessMatcher::Readable.into_box()), diff --git a/tests/test_find.rs b/tests/test_find.rs index 26f9a6d8..8dc3f6cf 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -1614,3 +1614,63 @@ fn find_exits_cleanly_on_broken_pipe() { "find panicked instead of exiting cleanly on a broken pipe:\n{stderr}" ); } + +// GNU find emits a warning when a -name/-iname pattern contains a directory +// separator (it matches basenames only, so the pattern can never match) and +// when a -path/-wholename pattern ends with '/'. find should warn on stderr +// but still exit successfully. See issue #783. +#[test] +fn name_pattern_with_separator_warns() { + ucmd() + .args(&["-name", "a/b"]) + .succeeds() + .stderr_contains("'-name' matches against basenames only") + .stderr_contains("directory separator ('/')") + .stderr_contains("Did you mean '-wholename'?"); + + ucmd() + .args(&["-iname", "a/b"]) + .succeeds() + .stderr_contains("'-iname' matches against basenames only"); +} + +#[test] +fn path_pattern_ending_with_separator_warns() { + ucmd() + .args(&["-path", "a/"]) + .succeeds() + .stderr_contains("-path a/ will not match anything because it ends with /."); + + ucmd() + .args(&["-wholename", "a/"]) + .succeeds() + .stderr_contains("-wholename a/ will not match anything because it ends with /."); + + ucmd() + .args(&["-ipath", "a/"]) + .succeeds() + .stderr_contains("-ipath a/ will not match anything because it ends with /."); +} + +#[test] +fn name_pattern_without_separator_does_not_warn() { + ucmd().args(&["-name", "a.txt"]).succeeds().no_stderr(); + ucmd().args(&["-path", "a.txt"]).succeeds().no_stderr(); + ucmd().args(&["-wholename", "a.txt"]).succeeds().no_stderr(); +} + +// A pattern made up only of '/' (e.g. `-name /` or `-path /`) is a legitimate +// way to match the root entry, so it must NOT trigger the separator warning +// (GNU itself gets this wrong — bug #62227). Covers the existing `find_slashes` +// behavior. +#[test] +fn all_slash_pattern_does_not_warn() { + ucmd() + .args(&["///", "-maxdepth", "0", "-name", "/"]) + .succeeds() + .no_stderr(); + ucmd() + .args(&["/", "-maxdepth", "0", "-path", "/"]) + .succeeds() + .no_stderr(); +}