Find directories, where files with a specific ending are missing

I want to display all directories, that do not contain files with a specific file ending. Therefore I tried using the following code:

find . -type d \! -exec test -e '{}/*.ENDING' \; -print

In this example I wanted to display all directories, that do not contain files with the ending .ENDING, but this does not work.

Where is my mistake?


Here's a solution in three steps:

temeraire:tmp jenny$ find . -type f -name \*ENDING -exec dirname {} \; |sort -u > /tmp/ending.lst
temeraire:tmp jenny$ find . -type d |sort -u > /tmp/dirs.lst
temeraire:tmp jenny$ comm -3 /tmp/dirs.lst /tmp/ending.lst 

Here we go!

#!/usr/bin/env python3
import os

for curdir,dirnames,filenames in os.walk('.'):
  if len(tuple(filter(lambda x: x.endswith('.ENDING'), filenames))) == 0:
    print(curdir)

Or alternately (and more pythonic):

#!/usr/bin/env python3
import os

for curdir,dirnames,filenames in os.walk('.'):
    # Props to Cristian Cliupitu for the better python
    if not any(x.endswith('.ENDING') for x in filenames):
        print(curdir)

Bonus DLC Content!

The (mostly) corrected version of the find command:

find . -type d \! -exec bash -c 'set nullglob; test -f "{}"/*.ENDING' \; -print

The shell expands the *, but in your case there's no shell involved, just the test command executed by find. Hence the file whose existence is tested, is literally named *.ENDING.

Instead you should use something like this:

find . -type d \! -execdir sh -c 'test -e {}/*.ENDING' \; -print

This would result in sh expanding *.ENDING when test is executed.

Source: find globbing on UX.SE


Inspired by Dennis Nolte's and MikeyB's answers, I came up with this solution:

find . -type d                                                           \
  \! -exec bash -c 'shopt -s failglob; echo "{}"/*.ENDING >/dev/null' \; \
  -print 2>/dev/null

It works based on the fact that

if the failglob shell option is set, and no matches are found, an error message is printed and the command is not executed.

By the way, that's why stderr was redirected to /dev/null.


I'd do it in perl personally

#!/usr/bin/perl

use strict;
use warnings;

use File::Find;


#this sub is called by 'find' on each file it traverses. 
sub checkdir {
    #skip non directories. 
    return unless ( -d $File::Find::name );

    #glob will return an empty array if no files math - which evaluates as 'false'
    if ( not glob ( "$File::Find::name/*.ENDING" ) ) {
        print "$File::Find::name does not contain any files that end with '.ENDING'\n";
    }
}

#kick off the search on '.' - set a directory path or list of directories to taste.
#  - you can specify multiple in a list if you so desire. 
find (  \&checkdir, "." );

Should do the trick (works on my very simplistic test case).