I'm not going to "code golf" this (shortest wins).. This is more like "code basketball".
Because your needs are a bit complex, I say use the most tunable option.
We launch with
Find (root directory) -type f | perl count21
Count21 is a file that contains some perl.
while (<>) { # read each line of input into variable $_
chomp $_; # Removing newline at end of line, if exists. It does.
local $/ = undef; # Input will ignore newlines and slurp in entire file
open (my $IN, "<", $_); # immune to < > ' " tab etc. in filename
my $text = <$IN>; # read entire file
chomp $text; # remove last newline if exists, so files with or
# without trailing newlines are the same.
$lc = ( $text =~ tr/\n// ) + 1; # tr/\n// counts newline characters.
# $text =~ says apply this operation to $text.
# The value of this operation is char count.
# Add 1 so we count the last line.
# This count will be wrong on 0-line files,
# but that's outside of problem scope.
if (21 >= $lc) { # if $lc <= 21 (Putting var first is bad luck)
print $_, "\n"; # print filename, and a newline
} # endif
} # end while(<>)
Bothering with all this perl and avoiding every shortcut seems super stupid. The reason to do it is you can far more easily cope with unexpected wrinkles, like the "some lines don't end in newline" problem, easily fixed with a chomp and a +1 here.
This also nukes-from-orbit the problem you'd have with passing file lists in shell, such as filenames with spaces, ", ', |, tab and other monkeywrenches. Unfortunately piping from Find won't handle newline in a filename, but the Perl File::Find module will.
It also makes it easy to shim in even more stuff: for instance if you only want files with "diddle", add
next if not ( $text =~ /diddle/i );
awk
docs having whatever the count of end of line sequences you want.find /DIR/ -exec awk PATTERN {} \;
.wc
is used for counting file contents, usewc -l
to count lines in files. Roll that up with a grep or two.