Bash length of string starting with "#" - linux

I am writing a script that reads input of color hex-code from user, i.e
#ffffff
I also need to check length of that string to be equal to 7 (1 hash symbol + 6 digits) and have "#" symbol as 1st character. Here's my code:
read color
until [ ${#color} -ge 7 ] && [ ${color:0:1} -eq "#" ]
do
echo "Color code must be 7 characters long and start with '#'!"
read color
done
But when I try to, I get an error
[: #: integer expression expected
It works perfect if string is not starting with hash.
Is there a way to make it work?

You are trying to do a numeric comparison -eq, instead of a string comparison:
${color:0:1} -eq "#"
should be
"${color:0:1}" = "#"
instead.
INTEGER1 -eq INTEGER2 - INTEGER1 is equal to INTEGER2
STRING1 = STRING2 - the strings are equal
(man test)

If you only need this to work in bash, use regular expression matching:
color=
until [[ $color =~ ^\#[[:xdigit:]]{6}$ ]]; do
IFS= read -r color
done
For any POSIX shell, use a case statement to allow pattern matching
color=
until :; do
IFS= read -r color
case $color in
\#[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]) break ;;
esac
done
or use the expr command:
color=
until IFS= read -r color && expr "$color" : "#[[:xdigit:]]\{6\}$"; do
:
done

Bash scripts use = for string comparison, so your code should be:
read color
until [ ${#color} -ge 7 ] && [ ${color:0:1} = "#" ]
do
echo "Color code must be 7 characters long and start with '#'!"
read color
done
It's saying integer expression expected because -eq is for integer comparisons

Related

Command output to string comparison in Zsh [duplicate]

This question already has answers here:
How to compare strings in Bash
(12 answers)
Closed 4 years ago.
I'm trying to get an if statement to work in Bash (using Ubuntu):
#!/bin/bash
s1="hi"
s2="hi"
if ["$s1" == "$s2"]
then
echo match
fi
I've tried various forms of the if statement, using [["$s1" == "$s2"]], with and without quotes, using =, == and -eq, but I still get the following error:
[hi: command not found
I've looked at various sites and tutorials and copied those, but it doesn't work - what am I doing wrong?
Eventually, I want to say if $s1 contains $s2, so how can I do that?
I did just work out the spaces bit... :/ How do I say contains?
I tried
if [[ "$s1" == "*$s2*" ]]
but it didn't work.
For string equality comparison, use:
if [[ "$s1" == "$s2" ]]
For string does NOT equal comparison, use:
if [[ "$s1" != "$s2" ]]
For the a contains b, use:
if [[ $s1 == *"$s2"* ]]
(and make sure to add spaces between the symbols):
Bad:
if [["$s1" == "$s2"]]
Good:
if [[ "$s1" == "$s2" ]]
You should be careful to leave a space between the sign of '[' and double quotes where the variable contains this:
if [ "$s1" == "$s2" ]; then
# ^ ^ ^ ^
echo match
fi
The ^s show the blank spaces you need to leave.
You need spaces:
if [ "$s1" == "$s2" ]
I suggest this one:
if [ "$a" = "$b" ]
Notice the white space between the openning/closing brackets and the variables and also the white spaces wrapping the '=' sign.
Also, be careful of your script header. It's not the same thing whether you use
#!/bin/bash
or
#!/bin/sh
Here's the source.
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash IMO.
Here are some examples Bash 4+:
Example 1, check for 'yes' in string (case insensitive):
if [[ "${str,,}" == *"yes"* ]] ;then
Example 2, check for 'yes' in string (case insensitive):
if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then
Example 3, check for 'yes' in string (case sensitive):
if [[ "${str}" == *"yes"* ]] ;then
Example 4, check for 'yes' in string (case sensitive):
if [[ "${str}" =~ "yes" ]] ;then
Example 5, exact match (case sensitive):
if [[ "${str}" == "yes" ]] ;then
Example 6, exact match (case insensitive):
if [[ "${str,,}" == "yes" ]] ;then
Example 7, exact match:
if [ "$a" = "$b" ] ;then
This question has already great answers, but here it appears that there is a slight confusion between using single equal (=) and double equals (==) in
if [ "$s1" == "$s2" ]
The main difference lies in which scripting language you are using. If you are using Bash then include #!/bin/bash in the starting of the script and save your script as filename.bash. To execute, use bash filename.bash - then you have to use ==.
If you are using sh then use #!/bin/sh and save your script as filename.sh. To execute use sh filename.sh - then you have to use single =. Avoid intermixing them.
I would suggest:
#!/bin/bash
s1="hi"
s2="hi"
if [ $s1 = $s2 ]
then
echo match
fi
Without the double quotes and with only one equals.
$ if [ "$s1" == "$s2" ]; then echo match; fi
match
$ test "s1" = "s2" ;echo match
match
$
I don't have access to a Linux box right now, but [ is actually a program (and a Bash builtin), so I think you have to put a space between [ and the first parameter.
Also note that the string equality operator seems to be a single =.
This is more a clarification than an answer! Yes, the clue is in the error message:
[hi: command not found
which shows you that your "hi" has been concatenated to the "[".
Unlike in more traditional programming languages, in Bash, "[" is a command just like the more obvious "ls", etc. - it's not treated specially just because it's a symbol, hence the "[" and the (substituted) "$s1" which are immediately next to each other in your question, are joined (as is correct for Bash), and it then tries to find a command in that position: [hi - which is unknown to Bash.
In C and some other languages, the "[" would be seen as a different "character class" and would be disjoint from the following "hi".
Hence you require a space after the opening "[".
Use:
#!/bin/bash
s1="hi"
s2="hi"
if [ "x$s1" == "x$s2" ]
then
echo match
fi
Adding an additional string inside makes it more safe.
You could also use another notation for single-line commands:
[ "x$s1" == "x$s2" ] && echo match
For a version with pure Bash and without test, but really ugly, try:
if ( exit "${s1/*$s2*/0}" )2>/dev/null
then
echo match
fi
Explanation: In ( )an extra subshell is opened. It exits with 0 if there was a match, and it tries to exit with $s1 if there was no match which raises an error (ugly). This error is directed to /dev/null.

How to check if package is installed, then ask if user wants to install package? [duplicate]

This question already has answers here:
How to compare strings in Bash
(12 answers)
Closed 4 years ago.
I'm trying to get an if statement to work in Bash (using Ubuntu):
#!/bin/bash
s1="hi"
s2="hi"
if ["$s1" == "$s2"]
then
echo match
fi
I've tried various forms of the if statement, using [["$s1" == "$s2"]], with and without quotes, using =, == and -eq, but I still get the following error:
[hi: command not found
I've looked at various sites and tutorials and copied those, but it doesn't work - what am I doing wrong?
Eventually, I want to say if $s1 contains $s2, so how can I do that?
I did just work out the spaces bit... :/ How do I say contains?
I tried
if [[ "$s1" == "*$s2*" ]]
but it didn't work.
For string equality comparison, use:
if [[ "$s1" == "$s2" ]]
For string does NOT equal comparison, use:
if [[ "$s1" != "$s2" ]]
For the a contains b, use:
if [[ $s1 == *"$s2"* ]]
(and make sure to add spaces between the symbols):
Bad:
if [["$s1" == "$s2"]]
Good:
if [[ "$s1" == "$s2" ]]
You should be careful to leave a space between the sign of '[' and double quotes where the variable contains this:
if [ "$s1" == "$s2" ]; then
# ^ ^ ^ ^
echo match
fi
The ^s show the blank spaces you need to leave.
You need spaces:
if [ "$s1" == "$s2" ]
I suggest this one:
if [ "$a" = "$b" ]
Notice the white space between the openning/closing brackets and the variables and also the white spaces wrapping the '=' sign.
Also, be careful of your script header. It's not the same thing whether you use
#!/bin/bash
or
#!/bin/sh
Here's the source.
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash IMO.
Here are some examples Bash 4+:
Example 1, check for 'yes' in string (case insensitive):
if [[ "${str,,}" == *"yes"* ]] ;then
Example 2, check for 'yes' in string (case insensitive):
if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then
Example 3, check for 'yes' in string (case sensitive):
if [[ "${str}" == *"yes"* ]] ;then
Example 4, check for 'yes' in string (case sensitive):
if [[ "${str}" =~ "yes" ]] ;then
Example 5, exact match (case sensitive):
if [[ "${str}" == "yes" ]] ;then
Example 6, exact match (case insensitive):
if [[ "${str,,}" == "yes" ]] ;then
Example 7, exact match:
if [ "$a" = "$b" ] ;then
This question has already great answers, but here it appears that there is a slight confusion between using single equal (=) and double equals (==) in
if [ "$s1" == "$s2" ]
The main difference lies in which scripting language you are using. If you are using Bash then include #!/bin/bash in the starting of the script and save your script as filename.bash. To execute, use bash filename.bash - then you have to use ==.
If you are using sh then use #!/bin/sh and save your script as filename.sh. To execute use sh filename.sh - then you have to use single =. Avoid intermixing them.
I would suggest:
#!/bin/bash
s1="hi"
s2="hi"
if [ $s1 = $s2 ]
then
echo match
fi
Without the double quotes and with only one equals.
$ if [ "$s1" == "$s2" ]; then echo match; fi
match
$ test "s1" = "s2" ;echo match
match
$
I don't have access to a Linux box right now, but [ is actually a program (and a Bash builtin), so I think you have to put a space between [ and the first parameter.
Also note that the string equality operator seems to be a single =.
This is more a clarification than an answer! Yes, the clue is in the error message:
[hi: command not found
which shows you that your "hi" has been concatenated to the "[".
Unlike in more traditional programming languages, in Bash, "[" is a command just like the more obvious "ls", etc. - it's not treated specially just because it's a symbol, hence the "[" and the (substituted) "$s1" which are immediately next to each other in your question, are joined (as is correct for Bash), and it then tries to find a command in that position: [hi - which is unknown to Bash.
In C and some other languages, the "[" would be seen as a different "character class" and would be disjoint from the following "hi".
Hence you require a space after the opening "[".
Use:
#!/bin/bash
s1="hi"
s2="hi"
if [ "x$s1" == "x$s2" ]
then
echo match
fi
Adding an additional string inside makes it more safe.
You could also use another notation for single-line commands:
[ "x$s1" == "x$s2" ] && echo match
For a version with pure Bash and without test, but really ugly, try:
if ( exit "${s1/*$s2*/0}" )2>/dev/null
then
echo match
fi
Explanation: In ( )an extra subshell is opened. It exits with 0 if there was a match, and it tries to exit with $s1 if there was no match which raises an error (ugly). This error is directed to /dev/null.

What will be the output of the following unix code?

a=x b=x
If [ $a -eq $b ]
Then
echo "a is equal to b"
else
echo "a is not equal to b"
fi
Will the above code successfully output "a is equal to b" or there is some error?
When looking at your script, you use the single bracket version of the test command. This is in contrast to the double-bracket version which is a shell-internal test.
The test command states:
INTEGER1 -eq INTEGER2 : INTEGER1 is equal to INTEGER2
But you are not comparing integers, but strings (unless x could be a dummy name for something that could be an integer.). Hence, the test will fail.
$ [ x -eq x ]
[: integer expression expected: x
So your output will read a is not equal to b.
If you want to compare strings, then it is advised to use the operator =:
STRING1 = STRING2 : the strings are equal
-eq is for integer comparisons, but you are comparing strings. Use = (or bash-ism ==):
[ "$a" = "$b" ]
Like i did, quote variable expansions to prevent word splitting and pathname expansion.

Test if string is "*"

For some reason I just cannot get an if statement to test if a string is literally equal to an asterisk. I have tried every combination I can think of and I don't want to mess with file globbing. Please help.
if [ $VAR = "\*" ]; then
* UPDATE *
Both of those suggestions work. The issue is apparently not with the * comparison, but with the other part of the if statement. This is supposed to compare whether or not $VAR is between 0 and 20 or is a wildcard.
if [ "$VAR" -gt 0 ] && [ "$VAR" -lt 20 ] || [ "$VAR" = "*" ]; then
This other part of the IF statement if apparently goofing up the last comparison.
* UPDATE *
Just tested it again and checked my syntax. When $VAR is between 0 and 20 it works great (true), when $VAR is over 20 it also works (reports false), however as soon as I try to set $VAR to an * the if statement freaks and pops out:
line 340: [: *: integer expression expected
Another version using bash's double brackets:
if [[ $VAR = "*" || ($VAR -gt 0 && $VAR -lt 20) ]]; then
The double brackets allow you to use && and ||. Also, bash doesn't perform word splitting or glob expansion on arguments to [[, so $VAR doesn't need to be quoted and ( doesn't need to be escaped.
[[ also works in zsh and ksh, if you need (some) portability.
$ VAR="*"
$ if [ "$VAR" = "*" ] ; then echo Star ; fi
Star
Quote variables when they could contain glob patterns, whitespace or other interpretable sequences you don't want interpreted. This also avoids syntax errors if $VAR is empty.
For your second problem, [ "$VAR" -gt 0 ] doesn't make sense if $VAR is anything but a number. So you must avoid having that test evaluated in that case. Simply exchange your tests - || and && are short-circuiting (in bash at least, not sure if that's POSIX):
if [ "$VAR" = "*" ] || [ "$VAR" -gt 0 -a "$VAR" -lt 20 ] ; then

Test whether string is a valid integer

I'm trying to do something common enough: Parse user input in a shell script. If the user provided a valid integer, the script does one thing, and if not valid, it does something else. Trouble is, I haven't found an easy (and reasonably elegant) way of doing this - I don't want to have to pick it apart char by char.
I know this must be easy but I don't know how. I could do it in a dozen languages, but not BASH!
In my research I found this:
Regular expression to test whether a string consists of a valid real number in base 10
And there's an answer therein that talks about regex, but so far as I know, that's a function available in C (among others). Still, it had what looked like a great answer so I tried it with grep, but grep didn't know what to do with it. I tried -P which on my box means to treat it as a PERL regexp - nada. Dash E (-E) didn't work either. And neither did -F.
Just to be clear, I'm trying something like this, looking for any output - from there, I'll hack up the script to take advantage of whatever I get. (IOW, I was expecting that a non-conforming input returns nothing while a valid line gets repeated.)
snafu=$(echo "$2" | grep -E "/^[-+]?(?:\.[0-9]+|(?:0|[1-9][0-9]*)(?:\.[0-9]*)?)$/")
if [ -z "$snafu" ] ;
then
echo "Not an integer - nothing back from the grep"
else
echo "Integer."
fi
Would someone please illustrate how this is most easily done?
Frankly, this is a short-coming of TEST, in my opinion. It should have a flag like this
if [ -I "string" ] ;
then
echo "String is a valid integer."
else
echo "String is not a valid integer."
fi
[[ $var =~ ^-?[0-9]+$ ]]
The ^ indicates the beginning of the input pattern
The - is a literal "-"
The ? means "0 or 1 of the preceding (-)"
The + means "1 or more of the preceding ([0-9])"
The $ indicates the end of the input pattern
So the regex matches an optional - (for the case of negative numbers), followed by one or more decimal digits.
References:
http://www.tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF
Wow... there are so many good solutions here!! Of all the solutions above, I agree with #nortally that using the -eq one liner is the coolest.
I am running GNU bash, version 4.1.5 (Debian). I have also checked this on ksh (SunSO 5.10).
Here is my version of checking if $1 is an integer or not:
if [ "$1" -eq "$1" ] 2>/dev/null
then
echo "$1 is an integer !!"
else
echo "ERROR: first parameter must be an integer."
echo $USAGE
exit 1
fi
This approach also accounts for negative numbers, which some of the other solutions will have a faulty negative result, and it will allow a prefix of "+" (e.g. +30) which obviously is an integer.
Results:
$ int_check.sh 123
123 is an integer !!
$ int_check.sh 123+
ERROR: first parameter must be an integer.
$ int_check.sh -123
-123 is an integer !!
$ int_check.sh +30
+30 is an integer !!
$ int_check.sh -123c
ERROR: first parameter must be an integer.
$ int_check.sh 123c
ERROR: first parameter must be an integer.
$ int_check.sh c123
ERROR: first parameter must be an integer.
The solution provided by Ignacio Vazquez-Abrams was also very neat (if you like regex) after it was explained. However, it does not handle positive numbers with the + prefix, but it can easily be fixed as below:
[[ $var =~ ^[-+]?[0-9]+$ ]]
Latecomer to the party here. I'm extremely surprised none of the answers mention the simplest, fastest, most portable solution; the case statement.
case ${variable#[-+]} in
*[!0-9]* | '') echo Not a number ;;
* ) echo Valid number ;;
esac
The trimming of any sign before the comparison feels like a bit of a hack, but that makes the expression for the case statement so much simpler.
I like the solution using the -eq test, because it's basically a one-liner.
My own solution was to use parameter expansion to throw away all the numerals and see if there was anything left. (I'm still using 3.0, haven't used [[ or expr before, but glad to meet them.)
if [ "${INPUT_STRING//[0-9]}" = "" ]; then
# yes, natural number
else
# no, has non-numeral chars
fi
For portability to pre-Bash 3.1 (when the =~ test was introduced), use expr.
if expr "$string" : '-\?[0-9]\+$' >/dev/null
then
echo "String is a valid integer."
else
echo "String is not a valid integer."
fi
expr STRING : REGEX searches for REGEX anchored at the start of STRING, echoing the first group (or length of match, if none) and returning success/failure. This is old regex syntax, hence the excess \. -\? means "maybe -", [0-9]\+ means "one or more digits", and $ means "end of string".
Bash also supports extended globs, though I don't recall from which version onwards.
shopt -s extglob
case "$string" of
#(-|)[0-9]*([0-9]))
echo "String is a valid integer." ;;
*)
echo "String is not a valid integer." ;;
esac
# equivalently, [[ $string = #(-|)[0-9]*([0-9])) ]]
#(-|) means "- or nothing", [0-9] means "digit", and *([0-9]) means "zero or more digits".
Here's yet another take on it (only using the test builtin command and its return code):
function is_int() { test "$#" -eq "$#" 2> /dev/null; }
input="-123"
if is_int "$input"
then
echo "Input: ${input}"
echo "Integer: ${input}"
else
echo "Not an integer: ${input}"
fi
You can strip non-digits and do a comparison. Here's a demo script:
for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09"
do
match=${num//[^[:digit:]]} # strip non-digits
match=${match#0*} # strip leading zeros
echo -en "$num\t$match\t"
case $num in
$match|-$match) echo "Integer";;
*) echo "Not integer";;
esac
done
This is what the test output looks like:
44 44 Integer
-44 44 Integer
44- 44 Not integer
4-4 44 Not integer
a4 4 Not integer
4a 4 Not integer
.4 4 Not integer
4.4 44 Not integer
-4.4 44 Not integer
09 9 Not integer
For me, the simplest solution was to use the variable inside a (()) expression, as so:
if ((VAR > 0))
then
echo "$VAR is a positive integer."
fi
Of course, this solution is only valid if a value of zero doesn't make sense for your application. That happened to be true in my case, and this is much simpler than the other solutions.
As pointed out in the comments, this can make you subject to a code execution attack: The (( )) operator evaluates VAR, as stated in the Arithmetic Evaluation section of the bash(1) man page. Therefore, you should not use this technique when the source of the contents of VAR is uncertain (nor should you use ANY other form of variable expansion, of course).
or with sed:
test -z $(echo "2000" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
# integer
test -z $(echo "ab12" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
# no integer
Adding to the answer from Ignacio Vazquez-Abrams. This will allow for the + sign to precede the integer, and it will allow any number of zeros as decimal points. For example, this will allow +45.00000000 to be considered an integer.
However, $1 must be formatted to contain a decimal point. 45 is not considered an integer here, but 45.0 is.
if [[ $1 =~ ^-?[0-9]+.?[0]+$ ]]; then
echo "yes, this is an integer"
elif [[ $1 =~ ^\+?[0-9]+.?[0]+$ ]]; then
echo "yes, this is an integer"
else
echo "no, this is not an integer"
fi
For laughs I roughly just quickly worked out a set of functions to do this (is_string, is_int, is_float, is alpha string, or other) but there are more efficient (less code) ways to do this:
#!/bin/bash
function strindex() {
x="${1%%$2*}"
if [[ "$x" = "$1" ]] ;then
true
else
if [ "${#x}" -gt 0 ] ;then
false
else
true
fi
fi
}
function is_int() {
if is_empty "${1}" ;then
false
return
fi
tmp=$(echo "${1}" | sed 's/[^0-9]*//g')
if [[ $tmp == "${1}" ]] || [[ "-${tmp}" == "${1}" ]] ; then
#echo "INT (${1}) tmp=$tmp"
true
else
#echo "NOT INT (${1}) tmp=$tmp"
false
fi
}
function is_float() {
if is_empty "${1}" ;then
false
return
fi
if ! strindex "${1}" "-" ; then
false
return
fi
tmp=$(echo "${1}" | sed 's/[^a-z. ]*//g')
if [[ $tmp =~ "." ]] ; then
#echo "FLOAT (${1}) tmp=$tmp"
true
else
#echo "NOT FLOAT (${1}) tmp=$tmp"
false
fi
}
function is_strict_string() {
if is_empty "${1}" ;then
false
return
fi
if [[ "${1}" =~ ^[A-Za-z]+$ ]]; then
#echo "STRICT STRING (${1})"
true
else
#echo "NOT STRICT STRING (${1})"
false
fi
}
function is_string() {
if is_empty "${1}" || is_int "${1}" || is_float "${1}" || is_strict_string "${1}" ;then
false
return
fi
if [ ! -z "${1}" ] ;then
true
return
fi
false
}
function is_empty() {
if [ -z "${1// }" ] ;then
true
else
false
fi
}
Run through some tests here, I defined that -44 is an int but 44- isn't etc.. :
for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09" "hello" "h3llo!" "!!" " " "" ; do
if is_int "$num" ;then
echo "INT = $num"
elif is_float "$num" ;then
echo "FLOAT = $num"
elif is_string "$num" ; then
echo "STRING = $num"
elif is_strict_string "$num" ; then
echo "STRICT STRING = $num"
else
echo "OTHER = $num"
fi
done
Output:
INT = 44
INT = -44
STRING = 44-
STRING = 4-4
STRING = a4
STRING = 4a
FLOAT = .4
FLOAT = 4.4
FLOAT = -4.4
INT = 09
STRICT STRING = hello
STRING = h3llo!
STRING = !!
OTHER =
OTHER =
NOTE: Leading 0's could infer something else when adding numbers such as octal so it would be better to strip them if you intend on treating '09' as an int (which I'm doing) (eg expr 09 + 0 or strip with sed)

Resources