0

I'd like to find all directories that contain both a Makefile and a file matching *.tex. The command find or locate easily finds one or the other. But how can those results be merged or intersected, to yield just the directories desired?

A useful answer generalizes to other filenames. Even better, it can merge more than two such queries.

(I don't see how to apply find's boolean operators, e.g. "and" expr1 -a expr2. Ditto for locate -A. Maybe two searches, strip the filenames and keep the paths, sort -u, into comm -12 <(cmd1) <(cmd2)?)

2 Answers2

4

Just use -a to 'AND' conditions:

find . -type d -exec test -f {}/Makefile \; -a -exec bash -c 'compgen -G '{}'/*.tex >/dev/null;exit $?' \; -print

In slow-mo:

  • -exec test -f {}/Makefile \; checks if Makefile exists in the directory
  • -exec bash -c 'compgen -G '{}'/*.tex >/dev/null;exit $?' checks if there are any *.tex files in the directory
  • the whole line tests true only if both test are true
  • in that case print the directory name

Ran on:

./no1
./no1/yes3
./no1/yes3/foo.tex
./no1/yes3/Makefile
./no1/no3
./no1/no
./no1/Makefile
./q
./no2
./no2/foo.tex
./yes1
./yes1/foo.tex
./yes1/Makefile
./yes2
./yes2/foo.tex
./yes2/Makefile

Gives:

./no1/yes3
./yes1
./yes2
xenoid
  • 10,597
1

find can't do "sub-queries" to print directories which contain some files, so comm is indeed the way to go:

comm -12 <(find . -name Makefile -exec dirname {} \; | sort ) <(find . -name '*.tex' -exec dirname {} \; | sort)

You could also loop over directories (recursively with globstar) which might be faster (compgen source):

for directory in */
do
    if [ -e "${directory}/Makefile" ] && compgen -G "${directory}/"*.tex > /dev/null
    then
        printf '%s\n' "${directory}"
    fi
done
l0b0
  • 7,453