178

I need to count the number of occurrences of a char in a string using Bash.

In the following example, when the char is (for example) t, it echos the correct number of occurrences of t in var, but when the character is comma or semicolon, it prints out zero:

var = "text,text,text,text" 
num = `expr match $var [,]`
echo "$num"
1

10 Answers 10

178

you can for example remove all other chars and count the whats remains, like:

var="text,text,text,text"
res="${var//[^,]}"
echo "$res"
echo "${#res}"

will print

,,,
3

or

tr -dc ',' <<<"$var" | awk '{ print length; }'

or

tr -dc ',' <<<"$var" | wc -c    #works, but i don't like wc.. ;)

or

awk -F, '{print NF-1}' <<<"$var"

or

grep -o ',' <<<"$var" | grep -c .

or

perl -nle 'print s/,//g' <<<"$var"
17
  • 2
    some more trick here like y="${x//[^s|S]}"; echo "${#y}" Commented Jun 13, 2014 at 0:11
  • 7
    use the first one, should always avoid resorting to spawning another process to do work like this, it can severely impact performance when using with large iteration loops. As a rule external process execution should be a last resort when using iterating or repeating operations. Commented Jun 21, 2014 at 10:17
  • 1
    @CiroSantilli六四事件法轮功包卓轩because for example echo -n some line | wc -l
    – clt60
    Commented Nov 20, 2015 at 2:04
  • 2
    @bgStack15 code block 1 does not initial additional process, and maybe has better performance if you have huge number of line to parsing.
    – petertc
    Commented Apr 1, 2016 at 6:01
  • 1
    Just FYI, tr -dc ',' <<<"$var" | awk '{ print length; }' seems to be way faster than the first option.
    – Bhushan
    Commented Jul 20, 2016 at 6:21
132

I would use the following awk command:

string="text,text,text,text"
char=","
awk -F"${char}" '{print NF-1}' <<< "${string}"

I'm splitting the string by $char and print the number of resulting fields minus 1.

If your shell does not support the <<< operator, use echo:

echo "${string}" | awk -F"${char}" '{print NF-1}'
9
  • 5
    @HattrickNZ Then use: $(grep -o "$needle" < filename | wc -l)
    – hek2mgl
    Commented Sep 11, 2014 at 8:26
  • 13
    @Amir What do you expect?
    – hek2mgl
    Commented Sep 12, 2014 at 9:02
  • 3
    You can skip the wc -l, just use grep -c, it works on both bsd grep and linux grep.
    – andsens
    Commented Aug 5, 2016 at 11:54
  • 8
    @andsens grep -c will only output the number of matching lines. It does not count multiple matches per line.
    – hek2mgl
    Commented Aug 5, 2016 at 12:19
  • 1
    I want to count '$'s in a string, how can I escape '$' from main string?
    – masT
    Commented Feb 6, 2018 at 12:22
113

You can do it by combining tr and wc commands. For example, to count e in the string referee

echo "referee" | tr -cd 'e' | wc -c

output

4

Explanations: Command tr -cd 'e' removes all characters other than 'e', and Command wc -c counts the remaining characters.

Multiple lines of input are also good for this solution, like command cat mytext.txt | tr -cd 'e' | wc -c can counts e in the file mytext.txt, even thought the file may contain many lines.

*** Update ***

To solve the multiple spaces in from of the number (@tom10271), simply append a piped tr command:

 tr -d ' '

For example:

echo "referee" | tr -cd 'e' | wc -c | tr -d ' '
2
  • 1
    In macOS, output contains mulitple spaces in front of the number
    – tom10271
    Commented Sep 25, 2020 at 2:06
  • Great, many thanks for the tr, it really sounds to be a great tool i didn't know about. Today i learned something new!
    – j.c
    Commented Jun 8, 2022 at 9:54
16

awk is very cool, but why not keep it simple?

num=$(echo $var | grep -o "," | wc -l)

You could then re-use it as a function:

# usage: echo "1,2,3,4,5" | text.count # outputs 4
function text.count(){
    grep -o "$1" | wc -l
}
7

Building on everyone's great answers and comments, this is the shortest and sweetest version:

grep -o "$needle" <<< "$haystack" | wc -l

2

awk works well if you your server has it

var="text,text,text,text" 
num=$(echo "${var}" | awk -F, '{print NF-1}')
echo "${num}"
1
  • Just as a note: awk -F, looks for a ,. You can do the following: awk -F"${your_char}"
    – Emixam23
    Commented Mar 19, 2019 at 14:49
2

also check this out, for example we wanna count t

echo "test" | awk -v RS='t' 'END{print NR-1}'

or in python

python -c 'print "this is for test".count("t")'

or even better, we can make our script dynamic with awk

echo 'test' | awk '{for (i=1 ; i<=NF ; i++) array[$i]++ } END{ for (char in array) print char,array[char]}' FS=""

in this case output is like this :

e 1
s 1
t 2
1

I Would suggest the following:

var="any given string"
N=${#var}
G=${var//g/}
G=${#G}
(( G = N - G ))
echo "$G"

No call to any other program

0

The awk solutions provided here so far all break if there's a line break in your text. E.g.:

text="one,two,thr
ee,four"
DELIM=','
count=$( awk -F"$DELIM" '{print NF-1}' <<<"${text}" )
echo $count

Result:

2
1

The solution that will also work correctly with line breaks is:

text="one,two,thr
ee,four"
DELIM=','
count=$( awk 'BEGIN{RS="'"$DELIM"'";FS=""}END{print NR-1}' <<<"${text}" )
echo $count

Result is 3.

0

Count fixed strings (-F) from a file

export searchpattern=$(echo ",")

echo "text,text,text,text" | tr "," '\n' | sed 's/$/,/g' > filename

export count=$(grep -F $searchpattern filename | wc -l)

echo "$count-1" | bc

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