Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

Is it possible to do a grep count of multiple occurrences in a file in one single command? For example:

$ cat > file
blah alfa
beta blah
blah blahgamma
gamma

I can do:

grep -c 'alfa' file 
1 
grep -c 'beta' file  
1
grep -c 'gamma' file  
2

But is it possible to so domething like:

grep -c -e 'alfa' -e 'beta' -e 'gamma' -somemoreblackmagic file

and get the counts for each of them?

alfa 1
beta 1
gamma 2
share|improve this question
2  
Couldn't you sort file | uniq -c and then grep on that? –  slhck Jan 8 '13 at 11:24
    
@slhck, please post that as an answer –  glenn jackman Jan 8 '13 at 14:12
    
@glenn Well, it doesn't follow the same output format, giving the count first, and then the keyword. If the OP thinks this is a solution, I'll be happy to provide a more detailed answer, of course :) –  slhck Jan 8 '13 at 14:16
    
@slhck just pipe an awk print $2 $1 on there –  maxwellb Jan 8 '13 at 18:30
1  
This answer assumes that the search strings will be complete lines (i.e., the only thing on any line where they occur). If the input is alfax, alfay, alfaz (on separate lines), this script will report 1 alfax / 1 alfay / 1 alfaz rather than 3 alfa. An input line of alfa beta (on the same line) will result in a report of 1 alfa beta rather than 1 alfa / 1 beta. –  Scott Jan 8 '13 at 18:38

5 Answers 5

up vote 2 down vote accepted

I don't think grep is capable of what you want to do.

Just use awk instead:-)

This solution may not work well for large files (is not optimized). And works for plain words only - not regexps. But it's easy to add some features if so desired.

Low end version with restrictions outlined in comments below:

awk '
{
    split($0, b); for (i in b) ++A[b[i]]
}
END {
    split("'"$*"'", a)
    for (i in a) print sprintf("%s %d", a[i], A[a[i]])
}
'

just give the search strings directly to the script

[EDIT]
fixed version with regex support (see comment below). Please tell me if there still are any open issues.

# ---- my favorite ----
awk -F' ?-c ' '
BEGIN { split("'"$*"'", a) }
{ for (i = 2; a[i]; ++i) if (match($0, a[i])) ++A[i] }
END { for (i = 2; a[i]; ++i) if (A[i]) print a[i] " " A[i] }
'
# ---- my favorite ----

sample usage:

script_name -c alfa -c beta -c gamma << !
alfa
beta
gamma
gamma
!

gives:

alfa 1
beta 1
gamma 2

regex usage:

script_name -c   "^al"    -c "beta" -c gamma -c "m.$" << !
alfa
beta
gamma
gamma
!

gives:

^al 1
beta 1
gamma 2
m.$ 2

[/EDIT]

share|improve this answer
    
(1) Big problem: this solution assumes that the search strings will be separate words. An input line of alfalfa would not count as an alfa. (2) Interesting feature: this solution counts every occurrence on every line. An input line of alfa alfa alfa would count as three occurrences of alfa. This might be desirable, but it’s not consistent with grep –c, which is what the question asked for. –  Scott Jan 8 '13 at 18:19
    
you are certainly right. I just added another solution that allows to specify an regexp as in egrep counting occurencies in lines as grep does. I hope it's more compatible now:-) –  sparkie Jan 8 '13 at 18:52
    
argument quoting still may have issues though –  sparkie Jan 8 '13 at 18:59
    
@sparkie, thanks, the non-regexp version takes about a 80-100 seconds on my average sized files, which is pretty decent. –  130490868091234 Jan 9 '13 at 10:41

Another awk solution, with shell script wrapper thrown in:

#!/bin/sh –
awk '
BEGIN { split("alfa beta gamma", keyword)
        for (i in keyword) count[keyword[i]]=0
}
/alfa/  { count["alfa"]++ }
/beta/  { count["beta"]++ }
/gamma/ { count["gamma"]++ }
END   {
        for (i in keyword) print keyword[i], count[keyword[i]]
}'

If you want to be able to choose the search keywords at runtime (and provide them as arguments, as in sparkie’s answer), this script can be adapted to build the awk script dynamically.

share|improve this answer
    
Thanks. +1 for scriptability, reusability, and readability! –  redstreet Jun 19 at 23:54

Perl solution:

perl -lne 'chomp;$s{$_}++ if /alpha|beta|gamma/ }{ print "$_ $s{$_}" for keys %s' file
share|improve this answer
    
This doesn't return the same result as grep -c if there is more than one word on each line. –  Thor Jan 9 '13 at 9:39

No grep cannot do this in one pass, I would suggest using awk:

awk -v pat='alfa beta gamma' '
  BEGIN { split(pat, p) } 

  { for(k in p) if($0 ~ p[k]) c[k]++ }

  END { for(k in p) print p[k], c[k]?c[k]:0 }
'

Or as a rather long one-liner:

awk -v pat='alfa beta gamma' 'BEGIN { split(pat, p) } { for(k in p) if($0 ~ p[k]) c[k]++ } END { for(k in p) print p[k], c[k]?c[k]:0 }'

Explanation

pat is split into the p array, which is then used to search for matches on each line ($0 ~ p[k]). The counters are held in the c array. The c[k]?c[k]:0 bit uses the ternary operator to print 0 when c[k] is zero.

Note if your pattern contains space, you need to use a different delimiter between the patterns in pat and to update the split command accordingly.

Testing

Input:

cat << EOF > file
alfa
beta
gamma
gamma
EOF

Output with pat='alfa beta gamma':

alfa 1
beta 1
gamma 2

Input:

cat << EOF > file
alfa beta
beta
gamma gamma
gamma alfa
alfalfa
alfa alfa
EOF

Output with pat='^a a$ alfa beta gamma':

beta 2                                          
gamma 2
^a 3
a$ 6
alfa 4

The output matches in both cases the output from running grep -c with each pattern individually.

share|improve this answer
    
(1) A minor point: unlike grep –c, this will not report 0 occurrences of any string that appears 0 times in the input. (2) Big problem: this solution assumes that the search strings will be the first word on a line. An input line of foo alfa would not count as an alfa. (3) Yet another problem: this solution assumes that the search strings will be separate words. If the input is alfax, alfay, alfaz (on separate lines), this script will report alfax 1 / alfay 1 / alfaz 1 rather than alfa 3. –  Scott Jan 8 '13 at 18:17
    
@Scott: you're right, this doesn't work like grep -c. I'll have another stab at it. –  Thor Jan 9 '13 at 9:11

I'd suggest to use uniq (with sort).

$ sort file | uniq -c
1 alfa
1 beta
2 gamma

You need sort if the file might not be sorted (in fact, only if the multiple occurences might not be on consecutive lines).

UPDATE:

Assuming that you have predefined patterns and they don't contain space:

$ PATTERNS='alfa beta gamma'

$ for P in $PATTERNS; do echo $P `grep -c $P file`; done
alfa 1
beta 1
gamma 2
share|improve this answer
    
Thanks I thought of this but the words to grep are not always at the beginning of the line. –  130490868091234 Jan 10 '13 at 8:16
    
Okay but now you changed your question to something different. You now imply that there is an explicit list given. I updated my answer as well then, see above. –  Daniel Sparing Jan 10 '13 at 13:11

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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