2

I have 150 directories in a parent directory. Some of them have .txt files, others do not. I would like to print which folders do NOT have a .txt file. How can I achieve this?

0

2 Answers 2

3
for dir in */; do
    set -- "$dir"/*.txt
    [ ! -e "$1" ] && printf '%s\n' "$dir"
done

This loops over all directories in the current directory. For each directory, it tries to expand a filename globbing pattern that would match all the .txt files in that directory, using set. This sets the positional parameters ($1, $2, etc.) to the pathnames of the files that matches the pattern, or it sets just $1 to the pattern itself if it does not match anything. If the first result of that filename expansion is not the pathname of an existing file, the name of the directory is printed.


To do this recursively for all directories in the current directory (including the current directory):

find . -type d -exec sh -c '
    for dir do
        set -- "$dir"/*.txt
        [ ! -e "$1" ] && printf "%s\n" "$dir"
    done' sh {} +

This uses find as a generator of directory pathnames. For batches of found directory pathnames, the same loop as earlier is executed, printing the pathname of each directory that does not contain any files whose name end in .txt.


Note that both above pieces of code will detect the absence of any name ending in .txt. If you are just interested in regular files (or symbolic links to regular files), then use the following as the body of the loop:

while [ "$#" -gt 0 ] && [ ! -f "$1" ]; do
    shift
done
[ ! -e "$1" ] && ...as above...

To include hidden filenames, use, for example, bash -O dotglob -c in place of sh -c with find. This will make the filename globbing in the shell loop also consider names starting with a dot. In the first variation, make sure to set the dotglob shell option with shopt -s dotglob if you are running the loop in bash.

3
  • Why do we need sh in the last line? Commented Mar 15, 2019 at 20:00
  • 2
    @WeijunZhou When you run sh -c with an in-line script and give it further arguments, the first argument will go into $0. This string will be used by the shell in any error messages. The rest of the arguments are assigned to the positional parameters. Note that $0 is not part of the positional parameters, so leaving the sh string out would cause the loop to skip the first directory in each batch that it receives from find.
    – Kusalananda
    Commented Mar 15, 2019 at 20:02
  • Now that you have considered "regular vs irregular files", why not consider "dotglob" as well? Commented Mar 15, 2019 at 20:41
1

I was able to accomplish this with the following:

#!/bin/bash

search_dir=~/tmp

dirs=( $( find "$search_dir" -type f -name "*.txt" -exec dirname {} \; | sort -u ) )
exclude=()

for dir in "${dirs[@]}"; do
    exclude+=( -not -path "$dir" )
done

find "$search_dir" "${exclude[@]}"  -type d 

Change the search_dir variable to the parent directory you want to work with.

It will then set the dirs array to every directory that does contain .txt files.

Then each of those directories will be added to the exclude array in a format that can be fed to find. We then search your parent directory again for all directories that are not in the exclude array.

You must log in to answer this question.

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