3

I am trying to count the occurrences of consonants in multiple files, but I want the number of occurrences to be separately calculated for each file.  I use

awk -v FS="" '{for ( i=1;i<=NF;i++){if($i ~/[bcdfghjklmnpqrtsvwxyzBCDEFGHJKLMNPQRTSVWXYZ]/) count_c++}} END {print FILENAME,count_c}' file1 file2

file1 looks like this:

bac Dfeg           
k87 eH

tRe        
rt up

file2 looks like this:

hi
rt2w
Prt

but it prints the occurrences of both files (output=file2 19). How could I change this so the output would be like:

file1 12
file2 7
10
  • 1
    Do you have GNU awk (gawk)? if so, you can use ENDFILE in place of END (and add a BEGINFILE rule to reset the counter) – steeldriver Mar 27 at 12:04
  • @steeldriver no I don't have it:(((,could it somehow be done with awk? – pleasehelp Mar 27 at 12:12
  • @steeldriver I tried gawk -v FS="" 'BEGINFILE{for ( i=1;i<=NF;i++){if($i ~/[bcdfghjklmnpqrtsvwxyzBCDEFGHJKLMNPQRTSVWXYZ]/) count_c++}} ENDFILE {print FILENAME,count_c}' file1 file2 but it isn't doing anything – pleasehelp Mar 27 at 12:33
  • @pleasehelp the BEGINFILE rule only needs to reset the counter. The actual matching/counting needs to remain in a separate rule that is applied to all records, like you originally had it. – steeldriver Mar 27 at 12:46
  • @steeldriver so where exactly should I put the BEGINFILE ?because everything I tried didn't work – pleasehelp Mar 27 at 12:58
6

With GNU awk for ENDFILE and IGNORECASE:

$ awk -v IGNORECASE=1 '
    { cnt += ( gsub(/[[:alpha:]]/,"&") - gsub(/[aeiou]/,"&") )}
    ENDFILE { print FILENAME, cnt+0; cnt=0 }
' file1 file2
file1 12
file2 7

or with any POSIX awk:

$ awk '
    { lc=tolower($0); cnt[FILENAME] += (gsub(/[[:alpha:]]/,"&",lc) - gsub(/[aeiou]/,"&",lc)) }
    END { for (i=1; i<ARGC; i++) print ARGV[i], cnt[ARGV[i]]+0 }
' file1 file2
file1 12
file2 7

If you only want to count the specific characters b, c, d, etc. instead of all alphabetic characters that aren't aeiou, then just change ( gsub(/[[:alpha:]]/,"&") - gsub(/[aeiou]/,"&") ) above to gsub(/[bcdfghjklmnpqrtsvwxyz]/,"&"))

Note that, unlike any approach that prints results in an FNR==1 clause, both of the above scripts will handle empty files correctly by printing the file name and 0 as the count.

Also note the cnt+0 in the first script - the +0 ensures that the value printed will be a numeric 0 rather than a null string if the first file is empty.

If the same file name can appear multiple times in the input then add FNR==1{cnt[FILENAME]=0} to the start of the script if you want it output multiple times or add if (!seen[ARGV[i]]++) { ... } around the print in the END section if you only want it output once.

See https://unix.stackexchange.com/a/642372/133219 for an answer to the followup question of also counting vowels.

1
  • No script in my answer does that so you'd have to show me (again, in your question, not in a comment) what script you actually ran for me to be able to tell you why it did that. Or just run exactly the script in my answer and then it won't do that. – Ed Morton Mar 27 at 16:02
2

FWIW, and probably not much, I'd be tempted to do this without awk:

consonants=bcdfghjklmnpqrtsvwxyz
for f in file*; do
    printf "%s %d\n" "$f" "$(< "$f" tr A-Z a-z | tr -dc "$consonants"  | wc -c)"
done

Though that, of course, is quite ASCII-centric (and if you have GNU tr, it doesn't deal with multibyte characters anyway.)

1
  • maybe expound on the ASCII-centric vs. multibyte chars just a bit, to clarify the benefits or shortcomings of this approach – ILMostro_7 Mar 27 at 14:53

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.