10

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?

HopelessN00b
  • 53,795
  • 33
  • 135
  • 209
bamboo
  • 101
  • 3
  • This is not a duplicate of [List directories not containing a file](http://serverfault.com/q/235025/4276) because that question doesn't deal with wildcards. – Cristian Ciupitu Jun 17 '14 at 00:04

8 Answers8

7

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 
Jenny D
  • 27,780
  • 21
  • 75
  • 114
  • 2
    One-liner without temporary files if using the Bash shell: `comm -3 <(find . -type f -name \*ENDING -exec dirname {} \; |sort -u) <(find . -type d |sort -u)` – Cristian Ciupitu Jun 16 '14 at 18:34
4

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
MikeyB
  • 39,291
  • 10
  • 105
  • 189
  • I think the **find** solutions fails with `test: ...: binary operator expected` if there are multiple `*.ENDING` files in a directory. – Cristian Ciupitu Jun 16 '14 at 17:03
  • `next(filter(lambda x: x.endswith('.ENDING'), filenames))` could also be written using generator comprehension i.e. `next(x for x in filenames if x.endswith('.ENDING'))`. – Cristian Ciupitu Jun 16 '14 at 17:55
  • @CristianCiupitu: Truth: the find command is hacky and not really fully tested. Generator comprehension - yeah that's another nice way to do it. – MikeyB Jun 16 '14 at 17:59
  • 1
    I think an even nicer solution would be to use `if not any(x.endswith('.ENDING') for x in filenames)` based on the fact that any returns `False` for an empty iterable. – Cristian Ciupitu Jun 16 '14 at 18:15
3

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

Dennis Nolte
  • 2,881
  • 4
  • 27
  • 37
  • Does not work. I get the following error: sh: `-c: line 0: syntax error near unexpected token `('`. My directory names have the format 'xyz (dfdf)'. In fact its a calibre library. – bamboo Jun 16 '14 at 14:15
  • 1
    When I try it, I get `sh: line 0: test: foo1/bar.ENDING: binary operator expected` for the directory containing a file with the ending `ENDING`. – Jenny D Jun 16 '14 at 14:19
  • This seems to work for me with plain a.ENDING filenames – user9517 Jun 16 '14 at 14:24
  • I have the following test directory structure: test->test (test)->test.ending After using this code I get `sh: -c: line 0: syntax error near unexpected token `(' sh: -c: line 0: 'test -e test (test)/*.ending' ./test (test)`. But when I change .ending to .xyz I get the same result. This is because i have bracts as directory name, right? How can I fix that? – bamboo Jun 16 '14 at 14:30
  • 3
    @bamboo I would give up with shell utilities and look for a different solution at this point. – user9517 Jun 16 '14 at 14:31
  • @bamboo please see the edit, i used the wrong formating, should work now (at least it did for me with bash.) – Dennis Nolte Jun 16 '14 at 14:46
  • @DennisNolte: Just a note: The shell will not expand the `*` if it is enclosed in single quotes like in `'test -e {}/*.ENDING'` – Sven Jun 16 '14 at 15:34
  • While this answer fails, I gave it +1 for explaining why OP's globbing failed. – Cristian Ciupitu Jun 16 '14 at 17:01
2

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.

Cristian Ciupitu
  • 6,396
  • 2
  • 42
  • 56
1

There are many solutions here but many involve using an interpreted language. @sk8asd123 's answer seems ince. Here is another one:

find . -iname "*ENDING" | sed 's/\(.*\/\)\(.*\)/\1/' | sort -u

It is kind of dirty and has different logic than the question:

  1. First find the files (any expression find can understand),
  2. remove the file names (part after last /), and finally
  3. find unique lines with the help of sort.

but the end result is what was desired; folders containing files matching "*ENDING".

1

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).

Sobrique
  • 3,747
  • 2
  • 15
  • 36
0

Heres a find one-liner.

find ./ -type d ! -regex '.*.ENDING$' -printf "%h\n" | sort -u

Edit: Oops, wont work either.

Matthew Ife
  • 23,357
  • 3
  • 55
  • 72
0
find . -type d '!' -exec sh -c 'ls -1 "{}"|egrep -q "*ENDING$"' ';' -print
  • q in egrep is for quiet

  • With egrep you can swap the regex that you need

  • ls -1 "{}" outputs the file names from the find command

masegaloeh
  • 18,236
  • 10
  • 57
  • 106
sk8asd123
  • 101
  • 2