186

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

188

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"
Sign up to request clarification or add additional context in comments.

18 Comments

some more trick here like y="${x//[^s|S]}"; echo "${#y}"
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.
@bgStack15 code block 1 does not initial additional process, and maybe has better performance if you have huge number of line to parsing.
Just FYI, tr -dc ',' <<<"$var" | awk '{ print length; }' seems to be way faster than the first option.
@Robert becase the wc incorrectly counts the number of lines, in the input. Of course, the wc -c is OK but because of the line counting problem I don't like it. example, the: printf "line1\nline2\n" | wc -l prints 2 but the printf "line1\nline2" | wc -l prints only 1.
Depends how you define a "line". For me, every line has to end with a newline character. The advantage is that you can count lines by parts - you can split aaa\nbbb\nccc into aa, a\nb, bb\ncc, c, count lines in all those parts and then just add them to get the result. This additive stability is important. You probably don't like cat too, since for files with data aaa\nbbb and ccc\nddd it joins the last "line" with the first one, creating bbbccc.
|
134

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 Comments

@HattrickNZ Then use: $(grep -o "$needle" < filename | wc -l)
@Amir What do you expect?
You can skip the wc -l, just use grep -c, it works on both bsd grep and linux grep.
@andsens grep -c will only output the number of matching lines. It does not count multiple matches per line.
I want to count '$'s in a string, how can I escape '$' from main string?
|
120

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 Comments

In macOS, output contains mulitple spaces in front of the number
Great, many thanks for the tr, it really sounds to be a great tool i didn't know about. Today i learned something new!
18

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
}

Comments

7

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

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

Comments

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 Comment

Just as a note: awk -F, looks for a ,. You can do the following: awk -F"${your_char}"
2

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

Comments

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

Comments

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.

Comments

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.