Search for directories containing one file and missing another

filesfind

I am currently aware of how to search directories and list those which do NOT contain a specific file like so:

find parent_directory -mindepth 1 -maxdepth 1 -type d '!' -exec sh -c 'ls -1 "{}"|egrep -i -q "^file_name$"' \; -print

but now I want to use the resulting directories to check whether or not they DO contain another file but I can't manage to do that in an extended one-line expression. Is this possible somehow?

Best Answer

You are making this far more complicated than needed. You don't seem to want to recurse into subdirectories, so all you need to find those directories that don't have a specific file is:

for dir in */; do [ ! -e "$dir"/"$filename" ] || printf '%s\n' "$dir"; done

And, to see which of those do have another file:

for dir in */; do 
    [ ! -e "$dir"/"$filename1" ] && 
    [ -e "$dir"/"$filename2" ] && 
    printf '%s\n' "$dir"; 
done

Or, in a slightly clearer syntax:

for dir in */; do 
    if [ ! -e "$dir"/"$filename1" ]; then 
        if [ -e "$dir"/"$filename2" ]; then 
            printf '%s\n' "$dir"; 
        fi 
    fi
 done

This is all done using the built-in tools of the shell. Specifically:

  • [ : this, and the accompanying ] are synonyms for the test builtin (see help [ or help test if using an sh-style shell). They are ways of writing test operations in the shell.
  • -e : this tests whether a file/directory etc exists. See help test. The simple format is: [ -e file ] which will return true if file exists.
  • [ ! -e filename ] : the ! simply inverses the test. So [ ! -e file ] will be true if file does not exist.

Taken together, this means the command above does:

## Iterate over every directory (dirs only because of the '/' in '*/')
## saving each of them in the variable $dir.
for dir in */; do 
    ## If this $dir does not contain $filename1
    if [ ! -e "$dir"/"$filename1" ]; then 
        ## If this $dir does contain $filename2
        if [ -e "$dir"/"$filename2" ]; then 
            ## Print the directory name
            printf '%s\n' "$dir"; 
        fi 
    fi
done

To run this, of course, you need to first set $filename1 and $filename2 accordingly. For example:

filename1="unwantedFile"
filename2="wantedFile"