6

I have lots of text files that have a different number of lines of text in them.

In Mac OS Finder, is there any way to search for files that are less than 21 lines? (i.e. each file has less than 21 line of text in them).

The Search for files gives lots of options, but I can't see one that is for line length.

From reading up on the subject, it seems using Grep in Terminal may be the best way, but I haven't found any sources that explain how to use Grep to search for line length on multiple files.

  • 1
    Would sort by size be helpful? Also, see apple.meta.stackexchange.com/questions/2180/… and include tag software recommendation if you want apps to do this. – ankii Sep 3 at 12:45
  • 2
    @ankiiiiiii No, sort by size doesn't take into account the lines of text in files. – big_smile Sep 3 at 12:56
  • 1
    I'm too lazy to answer, but you can filter using awk docs having whatever the count of end of line sequences you want. find /DIR/ -exec awk PATTERN {} \;. – 146438 Sep 3 at 12:58
  • 2
    The utility wc is used for counting file contents, use wc -l to count lines in files. Roll that up with a grep or two. – High Performance Mark Sep 3 at 13:10
  • Are all files in a single directory, or in nested directories? – jcaron Sep 4 at 9:57
12

In Terminal you can combine find and wc for this:

find /path/to/directory -type f \
    -exec bash -c '[[ $(wc -l < "$1") -lt 21 ]]' _ {} \; -print

This will find all files (-type f) beneath /path/to/directory, count the lines (wc -l < "{}", {} gets replaced by any file found) and print the file name for files containing less than 21 lines.

PS: It will also try to count the lines in non-text files. If this causes issues, use the following instead:

find /path/to/directory -type f -exec bash -c 'file -b "$1" | grep -q text' _ {} \; \
    -exec bash -c '[[ $(wc -l < "$1") -lt 21 ]]' _ {} \; -print

PPS: To start from the current directory, replace the path at the beginning with . (a single dot, for the current directory)

PPPS: To restrict the search to the current directory, use find . -maxdepth 1 -type f ...

5

Here's another shell solution. We start from the current working directory . -

find . -type f -exec wc -l {} + | sed '$d'| awk '$1 < 21 { $1=""; print}'

find filters just files and executes wc for all the files found. The output is piped to sed where we remove the last line which would be something like- ### Total.

awk then processes each line of output for the first column to be less than 21 lines and prints the filename in column 2 of the output on wards.

  • trying out the script, it tends to include those files that have exactly 21 lines, as opposed to those that have less than 21 lines. If I change the 21 in the script to 20, then it seems to find only those that have less than 21 lines. Is that expected behavior? Thanks! – big_smile Sep 4 at 12:09
  • 1
    @big-smile No, that's not the expected behavior. I can't reproduce your observation. Will test some more when I can find some time. – fd0 Sep 4 at 12:20
  • 1
    @big-smile OK, I found the bug. wc will not count the last line of a file if it is not terminated. – fd0 Sep 4 at 14:30
  • by terminated, do you mean an empty line at the end. None of my files have an empty line. – big_smile Sep 4 at 14:51
  • 1
    @big-smile No, the last line of the file will have text but it is not terminated with the newline character. – fd0 Sep 4 at 14:54
2

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

You must log in to answer this question.

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