I was wondering how to count the number of a specific character in each line by some text processing utilities?

For example, to count " in each line of the following text

Thank you!

The first line has two, and the second line has 0.

Another example is to count ( in each line.

  • 1
    Just going to add that you received much increased performance by writing your own 10 line C program for this rather than using regular expressions with sed. You should consider doing depending on the size of your input files. – user606723 Aug 14 '11 at 22:23

18 Answers 18

up vote 87 down vote accepted

You can do it with sed and awk:

$ sed 's/[^"]//g' dat | awk '{ print length }'

Where dat is your example text, sed deletes (for each line) all non-" characters and awk prints for each line its size (i.e. length is equivalent to length($0), where $0 denotes the current line).

For another character you just have to change the sed expression. For example for ( to:


Update: sed is kind of overkill for the task - tr is sufficient. An equivalent solution with tr is:

$ tr -d -c '"\n' < dat | awk '{ print length; }'

Meaning that tr deletes all characters which are not (-c means complement) in the character set "\n.

  • 2
    +1 should be more efficient than the tr&wc version. – Stéphane Gimenez Aug 14 '11 at 19:41
  • 1
    Yes, but can it handle Unicode? – amphetamachine Aug 15 '11 at 10:51
  • @amphetamachine, yes - at least a quick test with ß (utf hex: c3 9f) (instead of ") works as expected, i.e. tr, sed and awk do complement/replacement/counting without a problem - on a Ubuntu 10.04 system. – maxschlepzig Aug 15 '11 at 18:29
  • 1
    Most versions of tr, including GNU tr and classic Unix tr, operate on single byte characters and are not Unicode compliant.. Quoted from Wikipedia tr (Unix) .. Try this snippet: echo "aā⧾c" | tr "ā⧾" b ... on Ubuntu 10.04 ... ß is a single byte Extended Latin char and is handled by tr... The real issue here is not that tr doesn't handle Unicode (because ALL characters are Unicode), it is really that tr only handles one-byte at a time.. – Peter.O Aug 15 '11 at 19:32
  • @fred, no, ß is not a single byte character - its Unicode position is U+00DF, which is coded as 'c3 9f' in UTF-8, i.e. two bytes. – maxschlepzig Aug 16 '11 at 7:20

I would just use awk

awk -F\" '{print NF-1}' <fileName>

Here we set the field separator (with the -F flag) to be the character " then all we do is print number of fields NF - 1. The number of occurrences of the target character will be one less than the number of separated fields.

For funny characters that are interpreted by the shell you just need to make sure you escape them otherwise the command line will try and interpret them. So for both " and ) you need to escape the field separator (with \).

  • Maybe edit your answer to use singles quotes instead for escaping. It will work with any character (except '). Also, it has a strange behavior with empty lines. – Stéphane Gimenez Aug 15 '11 at 16:08
  • The question specifically uses " so I feel obliged to make the code work with it. It depends what shell you are using weather the character needs to be escaped but bash/tcsh will both need to escape " – Martin York Aug 15 '11 at 16:10
  • Of course, but there is no problem with -F'"'. – Stéphane Gimenez Aug 15 '11 at 16:12
  • +1 What a good idea to use FS.... This will resolve the blank-line showing -1, and, for example, the "$1" from the bash commandline. ... awk -F"$1" '{print NF==0?NF:NF-1}' filename – Peter.O Aug 15 '11 at 22:19
  • Also work with multiple chars as separator... useful ! – COil Sep 30 '16 at 15:35

Using tr ard wc:

function countchar()
    while IFS= read -r i; do printf "%s" "$i" | tr -dc "$1" | wc -m; done


$ countchar '"' <file.txt  #returns one count per line of file.txt

$ countchar ')'           #will count parenthesis from stdin
$ countchar '0123456789'  #will count numbers from stdin

The answers using awk fail if the number of matches is too large (which happens to be my situation). For the answer from loki-astari, the following error is reported:

awk -F" '{print NF-1}' foo.txt 
awk: program limit exceeded: maximum number of fields size=32767
    FILENAME="foo.txt" FNR=1 NR=1

For the answer from enzotib (and the equivalent from manatwork), a segmentation fault occurs:

awk '{ gsub("[^\"]", ""); print length }' foo.txt
Segmentation fault

The sed solution by maxschlepzig works correctly, but is slow (timings below).

Some solutions not yet suggested here. First, using grep:

grep -o \" foo.txt | wc -w

And using perl:

perl -ne '$x+=s/\"//g; END {print "$x\n"}' foo.txt

Here are some timings for a few of the solutions (ordered slowest to fastest); I limited things to one-liners here. 'foo.txt' is a file with one line and one long string which contains 84922 matches.

## sed solution by [maxschlepzig]
$ time sed 's/[^"]//g' foo.txt | awk '{ print length }'
real    0m1.207s
user    0m1.192s
sys     0m0.008s

## using grep
$ time grep -o \" foo.txt | wc -w
real    0m0.109s
user    0m0.100s
sys     0m0.012s

## using perl
$ time perl -ne '$x+=s/\"//g; END {print "$x\n"}' foo.txt
real    0m0.034s
user    0m0.028s
sys     0m0.004s

## the winner: updated tr solution by [maxschlepzig]
$ time tr -d -c '\"\n' < foo.txt |  awk '{ print length }'
real    0m0.016s
user    0m0.012s
sys     0m0.004s
  • + good idea! I expanded your table, in a new answer, feel free to edit (the final picture is not so clear, but I believe @maxschlepzig is steel the faster solution) – JJoao Mar 4 '15 at 8:35
  • maxschlepzig's solution is super fast! – okwap Apr 1 '16 at 6:36

Yet another implementation that does not rely on external programs, in bash, zsh, yash and some implementations/versions of ksh:

while IFS= read -r line; do 
  echo "${#line}"
done <input-file

Use line="${line//[!(]}"for counting (.

  • When the last line doesn't have a trailing \n, the while loop exits, because although it read the last line, it also returns a non-zero exit code to indicate EOF... to get around it, the following snippet works (..It has been been bugging me for a while, and I've just discovered this workaroung)... eof=false; IFS=; until $eof; do read -r || eof=true; echo "$REPLY"; done – Peter.O Aug 15 '11 at 21:42
  • @Gilles: you added a trailing / that is not needed in bash. It is a ksh requirement? – enzotib Aug 16 '11 at 7:35
  • 1
    The trailing / is needed in older versions of ksh, and IIRC in older versions of bash as well. – Gilles Aug 16 '11 at 8:15

Another possible implementation with awk and gsub:

awk '{ gsub("[^\"]", ""); print length }' input-file

The function gsub is the equivalent of sed's 's///g' .

Use gsub("[^(]", "")for counting (.

  • You can save one character, i.e. when removing the stdin redirection ... ;) – maxschlepzig Aug 14 '11 at 20:34
  • @maxschlepzig: yeah, of course ;) – enzotib Aug 14 '11 at 20:43
  • 1
    awk '{print gsub(/"/,"")}' input-file would be enough, as "For each substring matching the regular expression r in the string t, substitute the string s, and return the number of substitutions." (man awk) – manatwork Sep 6 '11 at 12:42

Another awk solution:

awk '{print gsub(/"/, "")}'

I decided to write a C program cause I was bored.

You should probably add input validation, but other than that's all set.

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
        char c = argv[1][0];
        char * line = NULL;
        size_t len = 0;
        while (getline(&line, &len, stdin) != -1)
                int count = 0;
                char * s = line;
                while (*s) if(*s++ == c) count++;
        if(line) free(line);
  • Thanks! Thanks for being bored so that I can learn something. Oh wait, do you need a return? – Tim Aug 14 '11 at 23:31
  • *shrugs*, if you want to be fully correct, you also need to add a few more #includes, but the default warnings on my compiler doesn't seem to care. – user606723 Aug 14 '11 at 23:39
  • You can leave out the free(line) because exiting the program implicitly frees all allocated memory - then there is place for a return 0; ... ;). Even in examples it is not good style to leave the return code undefined. Btw, getline is a GNU extension - in case someone is wondering. – maxschlepzig Aug 15 '11 at 6:04
  • @maxschlepzig: Is the memory pointed by line allocated by getline()? Is it allocated dynamically on heap by malloc or statically on stack? You said freeing it is not necessary, so is it not allocated dynamically? – Tim Aug 15 '11 at 6:28
  • 1
    @Tim, yes, e.g. if you refactor the code such that it is a standalone function - say -f, which is called several times from other code, then you have to call free after the last call of getline at the end of this function f. – maxschlepzig Aug 15 '11 at 7:44

For a string, the simplest would be with tr and wc (no need to overkill with awk or sed) - but note the above comments about tr, counts bytes, not characters -

echo $x | tr -d -c '"' | wc -m

where $x is the variable that contains the string (not a file) to evaluate.

  • worked like a charm. Thanks. – Nitin Mahesh Nov 5 '15 at 6:41

Here is another C solution that only needs STD C and less memory:

#include <stdio.h>

int main(int argc, char **argv)
  if (argc < 2 || !*argv[1]) {
    puts("Argument missing.");
    return 1;
  char c = *argv[1], x = 0;
  size_t count = 0;
  while ((x = getc(stdin)) != EOF)
    if (x == '\n') {
      printf("%zd\n", count);
      count = 0;
    } else if (x == c)
  return 0;
  • This will not report on the last line if it doesn't have a trailing '\n' – Peter.O Aug 15 '11 at 22:24
  • 1
    @fred, yes, which is on purpose, because a line without a trailing \n is no real line. This is the same behavior as with my other sed/awk (tr/awk) answer. – maxschlepzig Aug 16 '11 at 7:25

We can use grep with regex to make it more simple and powerful.

To count specific character.

$ grep -o '"' file.txt|wc -l

To count special characters including whitespace characters.

$ grep -Po '[\W_]' file.txt|wc -l

Here we are selecting any character with [\S\s] and with -o option we make grep to print each match (which is, each character) in separate line. And then use wc -l to count each line.

  • OP don't want to print number of all characters in a file! He wants to count/print number of a specific character. for example how many " are in each line; and for any other chars. see his question and also accepted answer. – αғsнιη Nov 23 '14 at 19:14

Maybe a more straight forward, purely awk answer would be to use split. Split takes a string and turns it into an array, the return value is the number of array items generated + 1.

The following code will print out the number of times " appears on each line.

awk ' {print (split($0,a,"\"")-1) }' file_to_parse

more info on split http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_92.html

Here is a simple Python script to find the count of " in each line of a file:

#!/usr/bin/env python2
with open('file.txt') as f:
    for line in f:
        print line.count('"')

Here we have used the count method of built-in str type.

For a pure bash solution (however, it's bash-specific): If $x is the variable containing your string:

echo ${#x2}

The ${x// thing removes all chars except ", ${#x2} calculates the length of this rest.

(Original suggestion using expr which has problems, see comments: )

expr length "${x//[^\"]/}"
  • Note that it's specific to GNU expr and counts bytes, not characters. With other expr: expr "x${x...}" : "x.*" - 1 – Stéphane Chazelas Nov 23 '14 at 21:27
  • Oh right, thanks! I've modified it using another idea I just had, which has the advantage of not using an external program at all. – Marian Mar 4 '15 at 23:08

Replace a by the char to be counted. Output is the counter for each line.

perl -nE 'say y!a!!'

Time comparison of the presented solutions (not an answer)

The efficiency of the answers is not important. Nevertheless, following @josephwb approach, I tried to time all the answers presented.

I use as input the Portuguese translation of Victor Hugo "Les Miserables" (great book!) and count the occurrences of "a". My edition has 5 volumes, many pages...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

C answers were compiled with gcc, (no optimizations).

Each answer was run 3 times and choose the best.

Don't trust too much these numbers (my machine is doing other tasks, etc, etc.). I share these times with you, because I got some unexpected results and I'm sure you will find some more...

  • 14 of 16 timed solutions took less then 1s; 9 less then 0.1s, many of them using pipes
  • 2 solutions, using bash line by line, processed the 30k lines by creating new processes, calculate the correct solution in 10s /20s.
  • grep -oP a is tree times faster then grep -o a (10;11 vs 12)
  • The difference between C and others is not so big as I expected. (7;8 vs 2;3)
  • (conclusions welcome)

(results in a random order)

=========================1 maxschlepzig
$ time sed 's/[^a]//g' mis.txt | awk '{print length}' > a2
real    0m0.704s ; user 0m0.716s
=========================2 maxschlepzig
$ time tr -d -c 'a\n' < mis.txt | awk '{ print length; }' > a12
real    0m0.022s ; user 0m0.028s
=========================3 jjoao
$ time perl -nE 'say y!a!!' mis.txt  > a1
real    0m0.032s ; user 0m0.028s
=========================4 Stéphane Gimenez
$ function countchar(){while read -r i; do echo "$i"|tr -dc "$1"|wc -c; done }

$ time countchar "a"  < mis.txt > a3
real    0m27.990s ; user    0m3.132s
=========================5 Loki Astari
$ time awk -Fa '{print NF-1}' mis.txt > a4
real    0m0.064s ; user 0m0.060s
Error : several -1
=========================6 enzotib
$ time awk '{ gsub("[^a]", ""); print length }' mis.txt > a5
real    0m0.781s ; user 0m0.780s
=========================7 user606723
#include <stdio.h> #include <string.h> // int main(int argc, char *argv[]) ...  if(line) free(line); }

$ time a.out a < mis.txt > a6
real    0m0.024s ; user 0m0.020s
=========================8 maxschlepzig
#include <stdio.h> // int main(int argc, char **argv){if (argc < 2 || !*argv[1]) { ...  return 0; }

$ time a.out a < mis.txt > a7
real    0m0.028s ; user 0m0.024s
=========================9 Stéphane Chazelas
$ time awk '{print gsub(/a/, "")}'< mis.txt > a8
real    0m0.053s ; user 0m0.048s
=========================10 josephwb count total
$ time grep -o a < mis.txt | wc -w > a9
real    0m0.131s ; user 0m0.148s
=========================11 Kannan Mohan count total
$ time grep -o 'a' mis.txt | wc -l > a15
real    0m0.128s ; user 0m0.124s
=========================12 Kannan Mohan count total
$ time grep -oP 'a' mis.txt | wc -l > a16
real    0m0.047s ; user 0m0.044s
=========================13 josephwb Count total
$ time perl -ne '$x+=s/a//g; END {print "$x\n"}'< mis.txt > a10
real    0m0.051s ; user 0m0.048s
=========================14 heemayl
#!/usr/bin/env python2 // with open('mis.txt') as f: for line in f: print line.count('"')

$ time pyt > a11
real    0m0.052s ; user 0m0.052s
=========================15 enzotib
$ time  while IFS= read -r line; do   line="${line//[!a]/}"; echo "${#line}"; done < mis.txt  > a13
real    0m9.254s ; user 0m8.724s
=========================16 bleurp
$ time awk ' {print (split($0,a,"a")-1) }' mis.txt > a14
real    0m0.148s ; user 0m0.144s
Error several -1
grep -n -o \" file | sort -n | uniq -c | cut -d : -f 1

where grep does all the heavy lifting: reports each character found at each line number. The rest is just to sum the count per line, and format the output.

Remove the -n and get the count for the whole file.

Counting a 1.5Meg text file in under 0.015 secs seems fast.
And does work with characters (not bytes).

A solution for bash. No external program called (faster for short strings).

If the value is in a variable:

$ a='"Hello!"'

This will print how many " it contains:

$ b="${a//[^\"]}"; echo "${#b}"

protected by Community Mar 3 '15 at 12:20

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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