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

Camille Goudeseune
  • 2,297
  • 4
  • 32
  • 41

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
  • 9,782
  • 4
  • 20
  • 31
  • 1
    +1. I never knew you could use `-exec` as a test; `man find` lists tests and actions separately and `-exec` is under "actions" there. Your answer is a learning material to me. – Kamil Maciorowski May 29 '17 at 18:24
  • Also for me... never used `compgen` before. – xenoid May 29 '17 at 20:13
  • This answer is not perfect by itself (e.g. it doesn't mention that `-a` connects tests by default, you may omit it) *but* what I learnt from it turned out to be so powerful (custom tests like [this](https://superuser.com/a/1221882/432690), yay!) I decided to award it. Keep up the good work. – Kamil Maciorowski Sep 23 '17 at 07:52
  • And when I started using Unix I thought find was an over complicated command :) – xenoid Sep 23 '17 at 08:02
  • When I learnt from this answer, I was not aware of this: [never embed `{}` in the shell code](https://unix.stackexchange.com/a/156010/108618). – Kamil Maciorowski Dec 01 '21 at 15:43
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,171
  • 4
  • 33
  • 54
  • This answer is easier to understand and to remember, but xenoid's is single-pass so it's faster on a big filesystem. (`locate` would be far faster!) – Camille Goudeseune May 30 '17 at 15:44