How can I find all subdirectories in a directory (only 1 level down) that do not contain a given file ?
22 Answers
There are several ways to accomplish this with find.
Approach 1 is less hacky and will work well with strange directory names (e.g., directory names containing newlines), but approach 2 should be faster if there are a lot of directories.
Approach 1
Command
find DIR -type d -mindepth 1 -maxdepth 1 -not -exec test -f {}/FILENAME \; \ -print | sortHow it works
find DIR -type d -mindepth 1 -maxdepth 1finds all directories (-type d) in DIR, with depth 1.-not -exec test -f {}/FILENAME \;is true if and only if a file called FILENAME could not be found in the currently processed directory ({}).-printwill output the desired directory names.If desired,
sortwill sort the output alphabetically.
Approach 2
Command
( find DIR -type f -mindepth 2 -maxdepth 2 -name FILENAME -printf "%h\n" ; \ find DIR -type d -mindepth 1 -maxdepth 1 ) | sort | uniq -uHow it works
find DIR -type f -mindepth 2 -maxdepth 2 -name FILENAMEfinds all files (-type f) called FILENAME in the subdirectories of DIR (files of directories of depth 1 have depth 2).-print "%h\n"print the names of the directories containing files named FILENAME, followed by a newline.find DIR -type d -mindepth 1 -maxdepth 1list all directories (-type d) in DIR, with depth 1.sortsorts the output alphabetically (output must be sorted when piping touniq).uniq -uprints only unique lines.Every subdirectory of DIR gets listed at least once, but those that contain a file called FILENAME get listed twice.
uniq -ueliminates the latter kind.
Directly in shell script:
for i in DIRECTORY/*/; do [ -f "$i/FILENAME" ] || basename "$i"; donefor i in DIRECTORY/*/uses shell expansion to safely (shouldn't be any problems with strange directory names) give all sub directories of a specific directory. Note the trailing slash to only give the directories.[ -f "$i/FILENAME" ]returns true if the file namedFILENAMEexists in the directory in this iteration. The||operator makes the following command run if the first one returned false, i.e. only if the file did not exist in the directory.basename "$i"prints the directory name (if the file name wasn't found therein). If you want the full path and not just the directory name, substitutebasenameforechoorreadlink -for something else per preference.
If you also want to include hidden directories, run (in Bash)
shopt -s dotglobbefore the command.