Search code examples
shellunixfindcommand-line-interface

`find` behaves unintuitively when pruning directories


I have few files under the current directory,

./a.txt
./b.txt
./dir1/c.txt
./dir1/d.txt

When I execute find . -path './dir1' -prune -o -name "*.txt",

as expected, it excludes everything under 'dir1' and prints,

./a.txt
./b.txt
./dir1

now if I switch the sequence of the subexpression and execute find . -name "*.txt" -o -path './dir1' -prune, to my understanding, it should have displayed all the files, as -name "*.txt" subexpression should have matched everything that has .txt extension and just or them with the following test. Therefore 'dir1' directory contents should not have been excluded. But that's doesn't seem to be the case. Displayed result is same as the previous one.

./a.txt
./b.txt
./dir1

what am I missing here?

My system is ubuntu-20.04. I have skim through the find man page and couldn't find any solution. Other sources in the web didn't provide with any satisfactory explanation either.


Solution

  • The -o is not applied in a complete run of find (ie, 'do all the things on the LH side of -o and if nothing is found then do the RH side...)

    Instead the -o is file by file which is a big difference. Essentially you have this loop:

    1. Find a file;
    2. apply all the expressions to that file;
    3. IF the overall expression is true, execute the action (which default is print)

    The other thing going on here is no combination of -o's or -a's or parenthesis will overcome that -prune has the highest precedence -- if a directory is listed with -prune it is never visited.

    If you want to do something along these lines:

    find_like . LH_do_first --or RH_do_if_LH_finds_nothing
    

    Then Bash is probably your best bet. Something along these lines:

    lh=$(find . -name "*.txt")
    cnt=$(awk '/[^[:blank:]]/{cnt++} END {print cnt}' <<<"$lh")
    if (( cnt > 0 )); then 
        printf "%s" "$lh"
    else
        find . -path "./dir1" -prune
    fi