1

I need to take a file and print the number of times each letter of the alphabet starts a word, in a descending order of word count. For example if the file is:

my nice name is Mike Meller 

then the output should be:

3 M
2 N
1 I

I need to do this in a single line. I know commands like wc -m and wc -w but I'm not sure how to iterate over each character and print it in the same way and then sort it like they want.

2

One way... (edited to avoid counting the same word twice)

$ echo "my nice name is Mike Meller" | tr ' ' '\n' | sort -f | uniq -i | sed -nr 's/^([a-z]).*/\U\1/Ip' | uniq -c | sort -r
  3 M
  2 N
  1 I
  • tr ' ' '\n' change spaces into newlines
  • sort -f sort the lines so same entries are together, even if case is different
  • uniq -i remove duplicate words, ignoring case
  • sed -nr 's/^([a-z]).*/\U\1/Ip' remove everything but the first letter, change all letters to upper case, and don't print the line if it doesn't start with a letter
  • uniq -c count the lines that are the same
  • sort -r sort descending

(replace echo "my nice name is Mike Meller" with cat name-of-your-file)

  • thanks but when i use this code the output is a list of words (and not of letters) and also there are numbers – UFC Insider Dec 2 '16 at 23:07
  • @UFCInsider Hmm I just made a small correction to get rid of the numbers (I added it in my explanation originally, but forgot to edit the full line) but the rest of the word should be gone, unless you have a different sed... hmm – Zanna Dec 2 '16 at 23:13
  • In your sed I think you need to define the group like this: \([a-z]\) ...edit: nope you were right. but for some reason i still see numbers in the list – UFC Insider Dec 2 '16 at 23:17
  • @UFCInsider you added -n and p? – Zanna Dec 2 '16 at 23:48
  • ok there is another problem... i need all distict words so the strategy of deleting all but the first letter of each word is not good – UFC Insider Dec 2 '16 at 23:51
1

With perl:

perl -Mopen=locale -lne '
  $c{uc $_}++ for /\b\p{Alpha}/g;
  END{for (sort {$c{$b} <=> $c{$a}} keys %c) {print "$c{$_} $_"}}'

Note that if some letters appear in a decomposed form. For instance, if É is entered as (that is E followed by a U+0301 combining accent) instead of the pre-composed É (U+00E9), then it will be counted as E, not nor É.

If that's a concern, then probably the best approach is to first decompose the text (since some graphems don't have a pre-composed form) and work on a graphem cluster basis. There are some like that you'd probably want to break down anyway:

Compare:

$ printf 'my fine name is \uc9ric, maybe E\u301ric, certainly not Eric\n' |
  perl -Mopen=locale -lne '
    $c{uc $_}++ for /\b\p{Alpha}/g;
    END{for (sort {$c{$b} <=> $c{$a}} keys %c) {print "$c{$_} $_"}}'
2 E
2 N
2 M
1 C
1 FI
1 É
1 I

with:

$ printf 'my fine name is \uc9ric, maybe E\u301ric, certainly not Eric\n' |
  perl -Mopen=locale -MUnicode::Normalize -lne '
    $c{uc $_}++ for NFKD($_) =~ /\b(?=\p{Alpha})\X/g;
    END{for (sort {$c{$b} <=> $c{$a}} keys %c) {print "$c{$_} $_"}}'
2 É
2 M
2 N
1 E
1 I
1 C
1 F
1

GNU awk:

gawk '
  { for (i=1; i<=NF; i++) count[toupper(substr($i,1,1))]++ } 
  END {
    PROCINFO["sorted_in"] = "@val_num_desc"
    for (key in count) print count[key], key
  }
' file 
0

Not a homework project, I hope? ;-) The tricky part is that you don't want to count the "L" in Meller twice, right? Hence the "distinct".

$cat t
my nice name is Mike Meller

And then a pipeline of commands to do the transforming:

$tr '[a-z]' '[A-Z]' < t |     # Convert all to upper case
fold -b -w 1 |                # Break into one letter per line
awk -f t.awk |                # Pipe the whole mess to awk to count
sort -r -n                    # Sort in reverse numeric order

The awk script is best broken into a separate file, though you can just put it all into a bash one-liner:

$cat t.awk    
/ / {                         # Match spaces,
  for (c in wc) {dc[c]+=1}    #  Accumulate word count (wc) into doc count (dc)
  split("",wc)                #  Reset the word count
}

!/ / {                        # Match non-spaces,
  if (wc[$1] == "") wc[$1]=1  #  If haven't already seen char in this word, mark it Donny
}

# Finally, output the count and the letter
END {
  for (c in wc) {dc[c]+=1}    # Accumulate one last time, in case there is no trailing space
  for (c in dc) {print c, dc[c]}
}

Which produces (for me) this output:

$tr '[a-z]' '[A-Z]' < t | fold -b -w 1 | awk -f t.awk  | sort -r -n
4 M
4 E
3 I
2 N
1 Y
1 S
1 R
1 L
1 K
1 C
1 A
  • Oh wow I think I made a mistake! I meant - for each letter of the english alphabet I want a counter of how many words started with that letter! – UFC Insider Dec 2 '16 at 20:40
  • Homework related questions seem fair game by my reckoning, so long as the asker still provides a good question. That means looking for the answer before just asking, and framing it into the simplest example of the problem possible. Homework questions can be great examples for things that are useful to know, we just don't want help vamparism and low quality questions. Looks like the asker put forward effort, so I'd say this question's fine either way. They may be a bit new to bash, but we all were once. – Centimane Dec 2 '16 at 20:56

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.