Search code examples
linuxshellfind

Find with shell with different and/or conditions


I'm trying to find files or folders in a directory based on different conditions:

  • all files that start with a W or,
  • all the files ending with an X or,
  • all files starting with Y and ending with Z!.

This has been my best bet so far:

find -name 'w*' || find -name '*x' || 'find -name 'y*' & find -name '*z!'

Which isn't working obviously. No error messages, just that it doesn't find all the files I'm looking for.


Solution

  • You can specify multiple predicates with -o to OR them.

    find -name 'w*' -o -name '*x' -o -name 'y*z!'
    

    Equivalently, you can use a single -regex which captures all these conditions.

    find -regex '.*/\(w[^/]*\|[^/]*x\|y[^/]*z!\)'
    

    (Notice how the argument to -regex is applied to the entire path, and how the regex syntax out of the box requires backslashes in some weird places compared to most modern regex dialects. Obviously, regex is different from regular wildcards; in a regular expression, .* matches any string, and [^/]* matches a sequence of characters which mustn't be a slash. The regex syntax here works with GNU find but not e.g. MacOS / BSD find; try adding an -E option and removing the backslashes before (, |, and ).)

    It's not clear what you hope || would do. It would run the second find if the first failed, but it won't (unless you give it invalid predicates or something; not finding any files is not an error).

    Even more bewilderingly, & runs the job in the background; you were probably looking for &&, but it also doesn't do what you want. What you tried could be done very inefficiently simply with

    find -name 'w*'
    find -name '*x'
    find -name 'y*z!'
    

    though the repeated traversals could possibly report the same file more than once. But you want to avoid that and only traverse the directory tree once, as that can be a very heavy operation. (Syntactically, the newline between the commands could be replaced with a semicolon if you want to force it to be a single line.)

    If you want to add additional criteria, perhaps notice how you then need parentheses to group the -o options:

    find -type f \( -name 'w*' -o -name '*x' -o -name 'y*z!' \)
    

    Tangentially, notice that in logical terms, you want to find the files which begin with w and the files which end with x etc. We are sometimes confused by how natural language terms like "and", "or", "but" map into different logical operators than we might expect. But if you rephrase it as "find all files whose name begins with w or ends with x etc" it flows naturally.