Bash count number of numbers

Viewed 866

I have several space separated strings:

1 -2 3  
1 23 456  
apple 69 b4n4n45 gr4pe5 -420 lol 10101010101 lol
1 23 a99 99a 9a9 -1 -23 -a99 -99a -9a9 --1 --23 --a99 --99a --9a9

I would like to know the exact number of numbers in each one of these strings. For the examples above the answers should be [ 3, 3, 3, 4]. I would not like to count strings like b4n4n45 as "numbers". Only pure integer strings should be counted.

I tried using grep and regex:

$ echo $string | grep '[0-9]' | wc

but that's not working out for me. Any tips?

8 Answers

Could you please try following(tested and written with shown samples), this should include negative numbers too, since OP has mentioned about integers so I have not considered counting floats. It gives output as 3 3 3 for mentioned 3 lines of samples.

awk '
{
  for(i=1;i<=NF;i++){
    if(length(int($i))==length($i)){ count++ }
  }
  print count
  count=""
}' Input_file

Bash count number of numbers

echo $string | grep -Po '(^|^-| | -)[0-9]+((?= )|$)' | wc -w

wc -w will count the number of space separated sub-strings in a given input
We can use grep -Po (-P for Perl regex, -o output ONLY matching) to match pure number sub-strings

We can use Regex to define a pattern of sub-strings we want to keep:

  1. (^|^-| | -) Starts with one of the four possible starting conditions
  2. [0-9]+ Contains only one or more integer characters
  3. ((?= )|$) Ends with a space or end of line character,(?= ) looks ahead for a space character but doesn't match, allowing overlap

With grep:

echo "$string" | grep -Ewo -- '-?[0-9]+' | wc -l
  • -?[0-9]+ is just an regular expression: optionally a hyphen followed by one or more digits.

  • -w is the secret sauce: that's the "word-regexp" option. The pattern is implicitly anchored with word boundary markers. This is what finds only the words that match the pattern. The GNU grep man page says:

    The test is that the matching substring must either be at the beginning of the line, or preceded by a non-word constituent character. Similarly, it must be either at the end of the line or followed by a non-word constituent character.

    That is what @Explorer is testing explicitly. The -w option wraps it up in a tiny little bow.

  • then, -o spits the matched words out, once per line, and wc -l counts them.

with plain bash

read -ra words <<<"$string"
count=0
for word in "${words[@]}"; do
  [[ $word == ?(-)+([0-9]) ]] && ((count++))
done
  • within [[...]] the == operator is a pattern matching operator that uses bash's extended patterns

Another awk:

$ awk '{for(i=1;i<=NF;i++)if($i+0||$i==0)c++;print c;c=0}' file

Output:

3
3
3

Edit: After my morning coffee I noticed pure integer strings so beware, this one counts decimal numbers also.

Perl Solution

$ perl -lane ' $c=0;for(@F) { if(/^-?\d+$/) { $c++; } } print $c ' lenna.txt
3
3
3
$

or

$ perl -lne ' $c=0; while(/\b-?\d+\b/g) { ++$c }  print $c  ' lenna.txt
3
3
3
$

With GNU awk for FPAT and assuming your input is only aphanumeric + minus signs as shown in your example:

$ awk -v FPAT='\\<-?[0-9]+\\>' '{print NF}' file
3
3
3

or with any awk for any space-separated strings:

$ awk '{c=0; for (i=1; i<=NF; i++) c+=($i ~ /^-?[0-9]+$/); print c}' file
3
3
3

This might work for you (GNU sed & bash):

sed -E 's/\s+$//;y/0./10/;s/\S+/$((&!=0))/g;s/\s+/+/g;s/.*/echo $((&))/e'

Remove any white space at the end of a line.

Covert any zero to one (we are not interested in the number only that it is a number).

Replace non-white space by a bash arithmetical expression which returns 1 for a number and 0 otherwise.

Replace white space by +'s

Evaluate the arithmetic expression created returning the count of numbers.

There is only a single regular expression that can pick up pure integers: [-+]?[0-9]+. When you combine this with gsub, you get the straightforward solution in awk:

$ awk '{t=OFS $0 OFS;gsub(FS,OFS OFS,t);print gsub(OFS"[-+]?[0-9]+"OFS,"",t)}'

First off, we use gsub to count the substitutions. gsub(ere,repl,str) makes a substitution of all substrings matching ere and replaces it with the string repl. It returns the total amount of substitutions done. This is exactly what we are interested in.

There is however still a problem. You cannot just substitute the count the matches of the regular expression [-+]?[0-9]+ as this would also count strings like foo123bar. To avoid this, we try to replace the integers sandwiched between the field separators.

Field separators, on the other hand are again tricky. They can be regular expressions themselves and the default FS=" " matches any white-space. Since the output-field separator is a fixed string, it is best to substitute FS by OFS. In short, the following would fail when working with a tsv-file:

 awk '{t=FS $0 FS; print gsub(FS"[-+]?[0-9]+"FS,"",t)}' file

While this is all nice, we still have to take care of one last thing: consecutive fields. gsub does not match with overlaps, hence we have to duplicate the output field separator.

Imagine OFS="_", then the string _123_456_, would lead to count one while _123__456_ would return two.

Related