I have a source code to find a number of words and characters in a file:

for i in `cat $1`
echo $j
c=`expr $j:'.*'`
echo "no of characters"  $cc
echo "no of words" $w

But when I run it in the terminal the following error message is displayed^ ./countWordChar 1.c hello ./countWordChar: line 10: 0+hello:.*: syntax error in expression (error token is ":.*") no of characters 0 no of words 1

The 10th line in the code is cc=$(($cc+$c)). Apparently the c variable's value is not a number of characters of a word but the word itself.

And my 1.c file content is like that:

hello world

What's wrong in the code?

PS. I know that there are builtin commands to count characters in a file but I must use the previous code according to my task.

  • The easy way : One simple command will print 1) number of lines 2) number of words 3) number of characters : $ cat file | wc ............ May 29 '16 at 14:05

The expr utility parses its arguments as an expression. The operators must appear as standalone arguments.

expr "$j" : '.*'

Above, expr is passed 4 arguments: expr, the content of $j, : and .*. Assuming the content of $j is not ( or ! (or things like length with some implementations), expr will under that as the : pattern matching operator applied to the content of $j.

To make it more robust, you'd want:

expr " $j" : '.*' - 1

(the second argument starting with a space cannot be recognised as an expr operator so that works around the problems noted above).


expr $j:'.*'

That would be two arguments (expr and the content of $j followed by :.* (assuming $j doesn't contain blank or wildcard characters, see below)). As expr only sees one argument (beside the command name), there's no operation requested, that's just one string argument which expr just echoes back.

Now, there are a number of other issues with your code:

Variable expansion and command substitution ($((...)) or the deprecated `...` form you use), when unquoted undergo split+glob. You do want the split part for the `cat $1` part (should have been $(cat < "$1")) to split it in words, but not the glob part, as otherwise that would expand * words for instance into the list of files in the current directory; and all the other variable expansions should be quoted (not necessary in assignments but quotes don't harm there).

Also, you can't use echo for arbitrary data.

So it should be:

w=0 c=0
set -f #  disable glob
for i in $(cat < "$1"); do
  printf '%s\n' "$i"
  w="$((w + 1))"
  c="$(expr " $i" : '.*' + "$c" - 1)"

With expr $j:'.*' the command expr is receiving one (1) argument.
The command expr could not understand that.

The command expr needs each argument clearly separated:

expr "$j" ":" '.*'

That will be three (3) arguments given to the command expr. The quotes " around : are not really required. And is better to use an space before the string in $j to avoid some misinterpretations, as this:

expr " $j" : '.*'

That would make your script similar to this:

w=0    cc=0
for i in `cat $1`; do
    echo "$j"
    c=`expr " $i" : '.*'`
echo "no of characters"  $cc
echo "no of words" $w

But that is more of a dash script than a bash script (that's how you tagged your question).
A simplified bash script will look as this:

w=0    cc=0
for i in $(< $1)
echo "no of characters"  "$cc"
echo "no of words" "$w"

The $(< $1) is equivalent to $(cat $1) but slightly faster. To increment w, is is shorter to use w++. And to count the number of characters we can use the length of $i as ${#i}.

Or even shorter:

w=0    cc=0
for i in $(< $1)
do  (( w++ , cc += ${#i} ))
printf "no of characters %s\nno of words %s\n"  "$cc" "$w"

By using bash (from 2.04-devel and up) comma , operator and using cc += ${#i} as the equivalent of cc = cc + ${#i}.

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.