0

I have multiple .txt files in one directory. Each file has a section:

DONE
item 1
item 2
item 3
DONE

I would like to count the number of lines between the two DONE markers for each file seperately.

I used this question to create this:

sed -n "/DONE/,/DONE/ p" *.txt | wc -l > ~/word_count.txt

But this combines the counts for each file into one number. Instead I would like output like this:

file1.txt 3
file2.txt 5
file3.txt 6
  • Is there a way to automate that process and amend word_count.txt each time? Or would I have to do it for each file manually by replacing *.txt – John Godlee Apr 21 '17 at 15:48
  • Dude, you just loop over the file names and append the result to word_count.txt e.g. for f in ./*.txt; do { printf '%s ' "$f"; sed -n '/DONE/,/DONE/p' "$f" | wc -l; } >>~/word_count.txt; done - sure the awk solution below is better (more efficient, faster) so should be preferred over any kind of loop. – don_crissti Apr 21 '17 at 15:50
  • I like this method, want to put it as an answer? – John Godlee Apr 21 '17 at 15:52
  • 1
    Play with it and once you figure out how it works explain that in an answer and you'll get at least one upvote ;) – don_crissti Apr 21 '17 at 15:54
2

Better use awk to count.

awk '
  FNR == 1 {inside = 0}
  $0 == "DONE" {
    if (inside) print FILENAME, n
    n = 0
    inside = ! inside
    next
  }
  inside {n++}' ./*.txt

That will print a record for each DONE...DONE section in each file, which means nothing will be printed if there's no such section. To print 0 for those, you'd need the GNU implementation of awk with its BEGINFILE, ENDFILE special statements:

awk '
  BEGINFILE {DONE_count = 0}
  $0 == "DONE" {
    if (++DONE_count % 2 == 0) print FILENAME, n
    n = 0
    next
  }
  DONE_count % 2 {n++}
  ENDFILE {if (!DONE_count) print FILENAME, 0}' ./*.txt

Or run one awk per file:

for file in ./*.txt; do
  awk '
    $0 == "DONE" {
      if (++DONE_count % 2 == 0) print FILENAME, n
      n = 0
      next
    }
    DONE_count % 2 {n++}
    END {if (!DONE_count) print FILENAME, 0}' "$file"
done
0
perl -lne '
   eof and !$a && print "$ARGV: ", 0+$a;          # no DONEs => ans=0
   next unless /DONE/ && !$a ... /DONE/;          # skip non-DONE ranges
   /DONE/ and !$a++ && next;                      # begin DONE range
   !/DONE/ and !eof and $a++,next;                # middle of DONE range
   !/DONE/ and eof and $a=2;                      # lone DONE => ans=0
   print "$ARGV: ", ($a-2, $a=0, close ARGV)[0];  # end of DONE range
                                                  # at the end we do 4 things: 1) subtract 2 from sum, 2) print filename+sum, 3) reset sum, and 4) skip the current file and jump to the next file in queue.
' ./*.txt

With sed we can do this on a per-file basis:

for f in ./*.txt; do
   printf '%s: %d\n' "$f" "$(sed -e '/DONE/,/DONE/!d; //d' "$f" | wc -l)"
done

The difference will be in the scenario when we shall not be having a closing DONE.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.