Your solution is pretty legible for the task, in my opinion. However, it's slow, because it spawns 3 processes per file. I reckon Awk is better suited here because it will allow to read a whole batch of files (as allowed by ARG_MAX) in a single go, using {} +
instead of {} \;
.
GNU Awk:
find . -type f -exec gawk '
BEGINFILE{c1=c2=c3=0}
/AAA/ {c1=1}
/BBB/||/CCC/{c2=1}
/DDD/ {c3=1; nextfile}
ENDFILE{if(c1 && c2 && !c3)print FILENAME}
' {} +
POSIX*:
find . -type f -exec awk '
FNR==1{
if(NR>1 && c1 && c2 && !c3)print f
c1=c2=c3=0
f=FILENAME
}
/AAA/ {c1=1}
/BBB/||/CCC/{c2=1}
/DDD/ {c3=1; nextfile}
END{if(c1 && c2 && !c3)print f}
' {} +
*Actually, nextfile
is still not POSIX but it has been accepted to the next issue of the standard. You can remove it for POSIX issue 7 compliance; the result will be the same, but with a performance penalty.
Note: Awk bails out if it does not have permissions to read a file. In GNU Find, simply add the -readable
flag to avoid that. If GNU Find is not available, Test can be used as an additional filter:
find . -type f -exec test -r {} \; -exec awk '
...
' {} +
But spawning a Test for each file represents a performance penalty.
Further reading:
-q
to stop as soon as the match has been determined. I would use-v
to egrep rather than-not
in find as I consider it more readable.-and
, since it's implicit between any actions if you don't specify-or
. (Like just before the-print
.) And as a nitpick,-and
and-not
aren't POSIX, the standard ones are-a
,-o
and!
. So, justfind . -type f -exec grep ... \; -exec grep ... \; ! -exec grep ... \; -print
might do.