72

I want to count number of words from a String using Shell.

Suppose the String is:

input="Count from this String"

Here the delimiter is space ' ' and expected output is 4. There can also be trailing space characters in the input string like "Count from this String ".

If there are trailing space in the String, it should produce the same output, that is 4. How can I do this?

kenorb
  • 155,785
  • 88
  • 678
  • 743
Yogesh Ralebhat
  • 1,376
  • 1
  • 13
  • 29

8 Answers8

103
echo "$input" | wc -w

Use wc -w to count the number of words.

Or as per dogbane's suggestion, the echo can be got rid of as well:

wc -w <<< "$input"

If <<< is not supported by your shell you can try this variant:

wc -w << END_OF_INPUT
$input
END_OF_INPUT
Tuxdude
  • 47,485
  • 15
  • 109
  • 110
  • 15
    That's a [Useless Use of Echo](http://fahdshariff.blogspot.com/2012/12/useless-use-of-echo.html). Use `wc -w <<< "$input"` instead. – dogbane Feb 27 '13 at 09:28
  • Thanks Tuxdude and dogbane for your replys. If I use wc -w <<< "$input" I am getting an error: **syntax error: got <&, expecting Word**. Any ideas? – Yogesh Ralebhat Feb 28 '13 at 08:15
  • Which shell are you running ? – Tuxdude Feb 28 '13 at 17:41
  • 4
    @dogbane, true, though only if you use bash. In standard POSIX shell, `echo` is the one-line way to do this. – Paul Draper Apr 05 '17 at 11:49
  • It's also just as easy to count words (`-w`) or characters (`-c`) of raw strings, e.g., `wc -c <<< "Count the number of characters in this string."` – Scott Gardner May 28 '20 at 22:11
56

You don't need an external command like wc because you can do it in pure bash which is more efficient.

Convert the string into an array and then count the elements in the array:

$ input="Count from this String   "
$ words=( $input )
$ echo ${#words[@]}
4

Alternatively, use set to set positional parameters and then count them:

$ input="Count from this String   "
$ set -- $input
$ echo $#
4
dogbane
  • 266,786
  • 75
  • 396
  • 414
  • 7
    The second variant has a side-effect that it would overwrite the positional parameters, like any received from the command line or parameters passed to a function (if these lines are within a function). So make sure not to rely on $1, $2, etc. after using set -- $input – Tuxdude Feb 27 '13 at 20:42
  • @dogbane Second solution suggested by you is working fine for me but as Tuxdude pointed out, I can not replace existing parameters with new one as it will break current flow. I tried to implement first solution but unfortunately I am getting error: **syntax error: got (, expecting Newline** – Yogesh Ralebhat Feb 28 '13 at 09:15
  • 1
    Important: there are no shortcuts here; `$ words=( "Count from this String " )` does **not** work. Bash is not [referentially transparent](https://en.wikipedia.org/wiki/Referential_transparency) – MSalters Jul 26 '19 at 15:40
  • @MSalters that's not true here. Here, the equivalent is `words=( Count from this String )`. With that, `echo ${#words[@]}` returns 4 as expected. If you double quote the string, you are giving a single element to the array. – juanmirocks Nov 19 '22 at 15:30
  • In zsh, `words=( $input )` will not work, because zsh doesn't split variables into words by default. This is instead necessary: `words=( ${=input} )` -- Ref: https://zsh.sourceforge.io/Doc/Release/Roadmap.html#General-Comments-on-Syntax – juanmirocks Nov 19 '22 at 16:20
10

Try the following one-liner:

echo $(c() { echo $#; }; c $input)

It basically defines c() function and passes $input as the argument, then $# returns number of elements in the argument separated by whitespace. To change the delimiter, you may change IFS (a special variable).

Community
  • 1
  • 1
kenorb
  • 155,785
  • 88
  • 678
  • 743
9

To do it in pure bash avoiding side-effects, do it in a sub-shell:

$ input="Count from this string "
$ echo $(IFS=' '; set -f -- $input; echo $#)
4

It works with other separators as well:

$ input="dog,cat,snake,billy goat,horse"
$ echo $(IFS=,; set -f -- $input; echo $#)
5
$ echo $(IFS=' '; set -f -- $input; echo $#)
2

Note the use of "set -f" which disables bash filename expansion in the subshell, so if the caller wants expansion it should be done beforehand (Hat Tip @mkelement0).

qneill
  • 1,643
  • 14
  • 18
  • 2
    Nicely done; I suggest prepending `set -f;` to each `set` command (note: must be a _separate_ command) so as to (temporarily) disable pathname expansion. This ensures that input tokens such as `*` aren't accidentally expanded. – mklement0 Apr 11 '14 at 04:21
6
echo "$input" | awk '{print NF}'
Henry Barber
  • 123
  • 1
  • 5
  • 1
    I like the fact that with `NF-x` where x is any number, you can strip away fields that are not to be counted. – Paulie-C Jun 12 '17 at 08:59
2
function count_item() {
   return $#
}
input="one two three"
count_item $input
n=$?
echo $n

NOTE: function parameter passing treat space as separated argument, therefore $# works. $? is the return value of the recently called function.

peter
  • 35
  • 4
1

I'll just chime in with a perl one-liner (avoiding 'useless use of echo'):

perl -lane 'print scalar(@F)' <<< $input
AAAfarmclub
  • 2,202
  • 1
  • 19
  • 13
0

It is efficient external command free way, like @dogbane's. But it works correctly with stars.

$ input="Count from *"
$ IFS=" " read -r -a words <<< "${input}"
$ echo ${#words[@]}
3

If input="Count from *" then words=( $input ) will invoke glob expansion. So size of words array will vary depending on count of files in current directory. So we use IFS=" " read -r -a words <<< "${input}" instead it.

see https://github.com/koalaman/shellcheck/wiki/SC2206

sir__finley
  • 140
  • 3
  • 6