Find command if filename doesn’t exists in directory

find

How I want to display the folder that do not have certain file. But the concern is, the file is same name but different cases.

Case study:
In tools directory, there are subdirectories that contain readme/README file and some of them does not have. For example

/toola/readme
/toolb/README
/toolc/ (does not have readme file)

I want find command to display only toolc folder by using this command.

find . -maxdepth 2 ! -name '*readme*' -o ! -name '*README*' | awk -F "/" '{print $$2}' | uniq

But it doesn't work. It display all file since toola doesn't have README and toolb doesn't have readme

Best Answer

You can't use find to look for files that do not exist. However, you may use find to look for directories, and then test whether the given filenames exists in those directories.

When using find to look for directories, make sure that you use -type d. Then test each of the found directories for the files README and readme.

Assuming the following directory hierarchy for some top-directory projects:

projects/
|-- toola
|   |-- doc
|   |-- readme
|   `-- src
|-- toolb
|   |-- doc
|   `-- src
|-- toolc
|   |-- README
|   |-- doc
|   `-- src
`-- toold
    |-- doc
    `-- src

Using find to find the directories directly under projects that does not contain a README or readme file:

$ find projects -mindepth 1 -maxdepth 1 -type d \
    ! -exec test -f {}/README ';' \
    ! -exec test -f {}/readme ';' -print
projects/toolb
projects/toold

Here, we find any directory directly under projects and then use the test utility to determine which one of the found directories do not contain either of the two files.

This is exactly equivalent of

find projects -mindepth 1 -maxdepth 1 -type d \
    -exec [ ! -f {}/README ] ';' \
    -exec [ ! -f {}/readme ] ';' -print

Another formulation of the above:

find projects -mindepth 1 -maxdepth 1 -type d -exec sh -c '
    for pathname do
        if [ ! -f "$pathname/README" ] &&
           [ ! -f "$pathname/readme" ]; then
            printf "%s\n" "$pathname"
        fi
    done' sh {} +

Here, we let a small in-line shell script do the actual testing for the two files and print the pathname of the directories that does not contain either of them. The find utility acts like a "pathname generator" of pathnames to directories for the in-line script to iterate over.

In fact, if the directory structure is like this, we may choose to not use find at all:

for pathname in projects/*/; do
    if [ ! -f "$pathname/README" ] &&
       [ ! -f "$pathname/readme" ]; then
        printf '%s\n' "$pathname"
    fi
done

Note the trailing slash in the projects/*/ pattern. It's this that makes the pattern only match directories (or symbolic links to directories).

A difference between doing it this way and using find is that with the above shell loop, we will exclude hidden directories under project and will include symbolic links to directories.

In all cases, we iterate over the pathnames of directories, and we test for the non-existence of the two filenames.

The only caveat is that the -f test will also be true for a symbolic link to a regular file.

Related:

Related Question