There are several issues with your idea.
First, Please, please, by all means: quote your variable expansions.
Quote
This is what happens here in some directory:
$ completestring=.* ; echo $completestring
. .. .directory .#screenon
Instead, I believe you want:
$ completestring=.* ; echo "$completestring"
.*
Using wc will count bytes, not characters (close to UNICODE code points). Example (in a console set for utf-8, almost all nowadays):
$ echo "école" | wc -c
7
$ echo "ß" | wc -c
3
Also, wc is counting the trailing new line.
$ echo "123" | wc -c
4
You need to use echo -n
(non-portable, not recommended) or printf '%s'
$ printf '%s' "123" | wc -c
3
Using an asterisk with grep makes it print runs of characters in each line:
$ completestring="jkfdsnlal92845t02u74ijopzidjb jd"
$ echo $completestring | grep -o [0-9]*
92845
02
74
There is no simple way to count that. A simplification is to use just the range:
$ echo $completestring | grep -o [0-9]
9
2
8
4
5
0
2
7
4
And then you can count lines:
$ echo $completestring | grep -o [0-9] | wc -l
9
Note: I'll use only a as variable from here on.
Is easier to type, hope you understand :).
echo $completestring | grep -o [0-9]*
You should avoid including the *
asterisk in the string under test if that is used for the end of the input. Depending on how you are reading the variable, maybe you can use Ctrl-D to signal an EOF
to the system to end reading input from the user.
Using full bash:
But we can do all what we need with simple bash constructs:
$ a="jkfdsnlal92845t02u74ijopzidjb jd"
$ b="${#a//[^0-9]}" # remove all characters
# that are not decimal digits
$ echo "${b}" # Not really needed, but this
928450274 # what var b contains.
$ echo "${#b}" # Print the length of var b.
9
What you wrote in your code could be translated to this (the /
needs to be quoted as \/
and I included the *
in the special list).
completestring=abc123*
dig=${completestring//[^0-9]}; dig=${#dig}
alpha=${completestring//[^a-zA-Z]}; alpha=${#alpha}
special=${completestring//[^,._+:@%\/*-]}; special=${#special}
echo "Digits=$dig Alpha=$alpha Special=$special"
Will print
Digits=3 Alpha=3 Special=1
LC_COLLATE
There is a gotcha with this system, however.
It will count many UNICODE characters as well:
$ c=aßbéc123*; a=${c//[^a-zA-Z]}; echo "string=$a count=${#a}"
string=aßbéc count=5
I believe that this is what you need.
But if you must limit to the 128 ascii characters, change LC_ALL or more specifically LC_COLLATE to the C locale when executing the range selection:
$ (LCcompletestring=abc123*; alpha=${completestring//[^a-zA-Z]}; alpha=${#alpha}; echo "${alpha}"_COLLATE=C a=${c//[^a-zA-Z]}; echo "string=$a count=${#a}")
string=abc count=3
The (…) is to use a sub-shell and avoid setting LC_COLLATE in the whole shell.
However you may set it at the start of your script and it will also work.
This got long, sorry. But anyuway: Am I missing something still?
Well, yes, I hope your passwords will not be including control characters (C0: ASCII from 1 to 31 and 127, and C1: 128 to 159). Because counting them has several twists. Probably outside of this answer.