16

How would I go into listing all directories that do not have a file with a given file name inside? e.g. given this tree

/
  /a
     README
     file001
     file002
  /b
     README
     file001
  /c
     file003

I want to list the directories that do not have a file named README, in this case it would be directory /c. How would I do that? I can't think of any syntax using e.g. find.

4

5 Answers 5

8

Assuming a find implementation like GNU find that accepts a {} embedded in an argument to -exec:

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

or, without the problematic embedding:

$ find . -type d ! -exec sh -c 'test -e "$1"/README' sh {} \; -print

Example

Here directories 1/1 through 5/5 have a README, the other dirs are empty.

$ tree 
.
|-- 1
|   `-- 1
|       `-- README
|-- 10
|   `-- 10
|-- 2
|   `-- 2
|       `-- README
|-- 3
|   `-- 3
|       `-- README
|-- 4
|   `-- 4
|       `-- README
|-- 5
|   `-- 5
|       `-- README
|-- 6
|   `-- 6
|-- 7
|   `-- 7
|-- 8
|   `-- 8
`-- 9
    `-- 9

Now when we run this version of our find command:

$ find . -type d \! -exec test -e '{}/README' \; -print
.
./10
./10/10
./7
./7/7
./9
./9/9
./6
./6/6
./5
./8
./8/8
./4
./1
./3
./2

References

3
  • How to modify the command to search for sub directories that dont have a specific file extension (say *.txt). Modifying README with *.txt doesnt seem to work Commented Apr 16, 2016 at 17:20
  • @WanderingMind - if you have a new question please ask it as a new one on the site ;-)
    – slm
    Commented Apr 16, 2016 at 20:53
  • What does the \! do? I'm having trouble googling that bit
    – dumbledad
    Commented Aug 31, 2020 at 8:33
8

No need for find. Just use the shell:

for d in */; do [ -f "$d"README ] || printf '%s\n' "$d"; done
c/

If you need it to be recursive, you can use (for bash, zsh can do this by default, use set -o globstar in ksh93):

shopt -s globstar
for d in **/; do [ -f "$d"README ] || printf '%s\n' "$d"; done

(note that dot-files are excluded by default).

3

You can use the -exec option of find to check for the file, and then print all results for which the check fails.

find /path/to/base -mindepth 1 -maxdepth 1 -type d -exec test -e {}/README \; -o -print
2

With zsh and glob qualifiers (estring):

print -rl -- *(/e_'[[ ! -f $REPLY/README ]]'_)

or

print -rl -- *(/^e_'[[ -f $REPLY/README ]]'_)

add D to include hidden directories:

print -rl -- *(D/e_'[[ ! -f $REPLY/README ]]'_)

/ selects only directories and e_'[[ ! -f $REPLY/README ]]'_ further selects only the directory names for which the shell code between the quotes returns true, that is for each directory name ($REPLY) that the glob *(/) expands to, it runs [[ ! -f $REPLY/README ]] and keeps the name of the directory if the result is true.
The second form ^e_'.....'_ uses the same glob qualifier, negated (but this time the conditional expression isn't negated: [[ -f $REPLY/README ]]).


The above will only return directory names in the current directory.
If you want to search recursively (again, to include hidden directories add the D qualifier):

print -rl ./**/*(/e_'[[ ! -f $REPLY/README ]]'_)
2

Portably, you could do:

find . -type d -exec sh -c '
  for dir do
    [ -f "$dir/README" ] || printf "%s\n" "$dir"
  done' sh '{}' +

[ -f file ] tests if the file exists and is confirmed to be a regular file (after symlink resolution).

If you wanted to test that it exists only (as an entry in that directory), regardless of its type, you'd need: [ -e file ] || [ -L file ], though note that you need search permission to the directory to perform those tests. You may want to add some [ -x "$dir" ] tests to account for those cases like:

find . -type d -exec sh -c '
  for dir do
    if [ -x "$dir" ]; then
      [ -f "$dir/README" ] || printf "%s\n" "$dir"
    else
      printf >&2 "Cannot tell for \"%s\"\n" "$dir"
    fi
  done' sh '{}' +

Or to avoid the race condition, with zsh:

find . -type d -exec zsh -c '
  zmodload zsh/system
  for dir do
    ERRNO=0
    if [ ! -f "$dir/README" ]; then
      if [ "$errnos[ERRNO]" = ENOENT ]; then
        printf "%s\n" "$dir"
      else
        syserror -p "ERROR: $dir/README: "
      fi
    fi
  done' zsh '{}' +

See also How do I tell if a regular file does not exist in Bash? on SO.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .