Why do two empty strings compare as not equal? - string

my#comp:~/wtfdir$ cat wtf.sh
str1=$(echo "")
str2=$(echo "")
if [ $str1 != $str2 ]; then
echo "WTF?!?!"
fi
my#comp:~/wtfdir$ ./wtf.sh
WTF?!?!
my#comp:~/wtfdir$
WTF is going on here?!
How I wrote the above code: Googling "bash compare strings" brought me to this website which says:
You can check the equality and inequality of two strings in bash by using if statement. “==” is used to check equality and “!=” is used to check inequality of the strings.
Yet I'm getting the above?
What am I not understanding? What am I doing wrong?

You aren't running a comparison at all, because you aren't using quotes where they're mandatory. See the warning from http://shellcheck.net/ about unquoted expansions at SC2086.
If both string are empty, then:
[ $str1 != $str2 ]
...evaluates to...
[ != ]
...which is a test for whether the string != is nonempty, which is true. Change your code to:
[ "$str1" != "$str2" ]
...and the exact values of those strings will actually be passed through to the [ command.
Another alternative is using [[; as described in BashFAQ #31 and the conditional expression page on the bash-hackers' wiki, this is extended shell syntax (in ksh, bash, and other common shells extending the POSIX sh standard) which suppresses the string-splitting behavior that's tripping you up:
[[ $str1 != "$str2" ]]
...requires quotes only on the right-hand side, and even those aren't needed for the empty-string case, but to prevent that right-hand side from being treated as a glob (causing the comparison to always reflect a match if str2='*').

Related

In 'sh' how to put the result of a string comparison into a variable?

In basic 'shell', how do I put the result of a string comparison into a boolean variable?
Consider:
isweekday() {
w=$(date +%w)
if [[ $w == "0" ]] || [[ $w == "6" ]]; then
false
else
true
fi
}
One can debate whether it would be clearer or not, but is there a way to assign the result of the 'if' expression to a boolean variable, e.g.
isweekday() {
w=$(date +%w)
wd=<boolean result of [[ $w == "0" ]] || [[ $w == "6" ]]>
return $wd
}
After some experimentation, I got closer to what I want but it still isn't what I'm after:
isweekday() {
w=$(date +%w)
[[ $w == "0" ]] || [[ $w == "6" ]]
}
This works and does not require the conditional, but it looks wrong, however if you do:
if isweekday; then
echo 'weekday'
fi
you will get the right result. This seems to be because the exit code of 'true' is 0 and the exit code of 'false' is 1 (not quite sure why that is...)
There are no boolean variables. All shell variables are strings (though there are limited facilities for interpreting them as integer numbers for basic comparisons etc and basic arithmetic).
The exit code 0 is reserved for success; all other exit codes (up to the maximum 255) signify an error (though some nonstandard tools use this facility to communicate non-error results by way of convention; the caller is then assumed to understand and agree on this ad-hoc use of the exit code).
Perhaps then the simplest answer to what you appear to be asking is to save the exit code. It is exposed in the variable $?. But very often, you should not need to explicitly examine its value; indeed, your final isweekday code looks by far the most idiomatic and natural, and returns this code from [[ implicitly.
I don't understand why you think your final code "looks wrong"; this is precisely how you would use a function in a conditional.
However, in some sense, a case statement would look less cluttered here (but this syntax is somewhat jarring at first for other reasons, until you get used to it).
is_weekday () {
case $(date +%w) in
0 | 6) return 1;;
esac
return 0
}
This is portable to POSIX sh, unlike the Bash-only [[...]] syntax you were using. Perhaps see also Difference between sh and bash
The standard approach in Shell is to use the exit code of a command as the true/false value where zero indicates true and non-zero false. Also, I would instead define a function which tests if it's weekend today since that is the exception so to speak. It's also a good idea to define w as a local variable and to quote all variables which could in the general case contain a space or be undefined, something which can lead to a syntax error after variable expansion. Here is my suggestion:
IsWeekend()
{
local w="$(date +%w)"
[ "$w" = 0 ] || [ "$w" = 6 ]
}

Having trouble with simple Bash if/elif/else statement

I'm writing bash scripts that need to work both on Linux and on Mac.
I'm writing a function that will return a directory path depending on which environment I'm in.
Here is the pseudo code:
If I'm on a Mac OS X machine, I need my function to return the path:
/usr/local/share/
Else if I'm on a Linux machine, I need my function to return the path:
/home/share/
Else, you are neither on a Linux or a Mac...sorry.
I'm very new to Bash, so I apologize in advance for the really simple question.
Below is the function I have written. Whether I'm on a Mac or Linux, it always returns
/usr/local/share/
Please take a look and enlighten me with the subtleties of Bash.
function get_path(){
os_type=`uname`
if [ $os_type=="Darwin" ]; then
path="/usr/local/share/"
elif [ $os_type=="Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
echo $path
}
You need spaces around the operator in a test command: [ $os_type == "Darwin" ] instead of [ $os_type=="Darwin" ]. Actually, you should also use = instead of == (the double-equal is a bashism, and will not work in all shells). Also, the function keyword is also nonstandard, you should leave it off. Also, you should double-quote variable references (like "$os_type") just in case they contain spaces or any other funny characters. Finally, echoing an error message ("...not supported") to standard output may confuse whatever's calling the function, because it'll appear where it expected to find a path; redirect it to standard error (>&2) instead. Here's what I get with these cleaned up:
get_path(){
os_type=`uname`
if [ "$os_type" = "Darwin" ]; then
path="/usr/local/share/"
elif [ "$os_type" = "Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported" >&2
exit 1
fi
echo "$path"
}
EDIT: My explanation of the difference between assignments and comparisons got too long for a comment, so I'm adding it here. In many languages, there's a standard expression syntax that'll be the same when it's used independently vs. in test. For example, in C a = b does the same thing whether it's alone on a line, or in a context like if ( a = b ). The shell isn't like that -- its syntax and semantics vary wildly depending on the exact context, and it's the context (not the number of equal signs) that determines the meaning. Here are some examples:
a=b by itself is an assignment
a = b by itself will run a as a command, and pass it the arguments "=" and "b".
[ a = b ] runs the [ command (which is a synonym for the test command) with the arguments "a", "=", "b", and "]" -- it ignores the "]", and parses the others as a comparison expression.
[ a=b ] also runs the [ (test) command, but this time after removing the "]" it only sees a single argument, "a=b" -- and when test is given a single argument it returns true if the argument isn't blank, which this one isn't.
bash's builtin version of [ (test) accepts == as a synonym for =, but not all other versions do.
BTW, just to make things more complicated bash also has [[ ]] expressions (like test, but cleaner and more powerful) and (( )) expressions (which are totally different from everything else), and even ( ) (which runs its contents as a command, but in a subshell).
You need to understand what [ means. Originally, this was a synonym for the /bin/test command. These are identical:
if test -z "$foo"
then
echo "String '$foo' is null."
fi
if [ -z "$foo" ]
then
echo "String '$foo' is null."
fi
Now, you can see why spaces are needed for all of the parameters. These are parameters and not merely boolean expressions. In fact, the test manpage is a great place to learn about the various tests. (Note: The test and [ are built in commands to the BASH shell.)
if [ $os_type=="Darwin" ]
then
This should be three parameters:
"$os_type"
= and not ==
"Darwin"
if [ "$os_type" = "Darwin" ] # Three parameters to the [ command
then
If you use single square brackets, you should be in the habit to surround your parameters with quotation marks. Otherwise, you will run into trouble:
foo="The value of FOO"
bar="The value of BAR"
if [ $foo != $bar ] #This won't work
then
...
In the above, the shell will interpolate $foo and $bar with their values before evaluating the expressions. You'll get:
if [ The value of FOO != The value of BAR ]
The [ will look at this and realize that neither The or value are correct parameters, and will complain. Using quotes will prevent this:
if [ "$foo" != "$bar" ] #This will work
then
This becomes:
if [ "The value of FOO" != "The value of BAR" ]
This is why it's highly recommended that you use double square brackets for your tests: [[ ... ]]. The test looks at the parameters before the shell interpolates them:
if [[ $foo = $bar ]] #This will work even without quotation marks
Also, the [[ ... ]] allows for pattern matching:
if [[ $os_type = D* ]] # Single equals is supported
then
path="/usr/local/share/"
elif [[ $os_type == L* ]] # Double equals is also supported
then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
This way, if the string is Darwin32 or Darwin64, the if statement still functions. Again, notice that there has to be white spaces around everything because these are parameters to a command (actually, not anymore, but that's the way the shell parses them).
Adding spaces between the arguments for the conditionals fixed the problem.
This works
function get_path(){
os_type=`uname`
if [ $os_type == "Darwin" ]; then
path="/usr/local/share/"
elif [ $os_type == "Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
echo $path
}

Compare values in Linux

I'm using .conf which contain keys and values.
Some keys contains numbers like
deployment.conf
EAR_COUNT=2
EAR_1=xxx.ear
EAR_2=yyy.ear
When I try to retrieve that value using particular key and compare with integer value i.e. natural number.
But Whatever I retrieved values from .conf ,it is should be String datatype.
How should I compare both value in Linux Bash script.
Simply : How should I compare two values in Linux.?
Ex :
. ./deployment.conf
count=$EAR_COUNT;
echo "count : $count";
if [ $count -gt 0 ]; then
echo "Test"
fi
I'm getting following error :
count : 2
: integer expression expected30: [: 2
They're all strings in bash, notwithstanding your ability to do typeset-type things to flag them differently.
If you want to do numeric comparisons, just use -eq (or its brethren like -gt, -le) rather than ==, != and so on:
if [[ $num -eq 42 ]] ; then
echo Found the answer
fi
The full range of comparison operators can be found in the bash manpage, under CONDITIONAL EXPRESSIONS.
If you have something that you think should be a number and it's not working, I'll warrant it's not a number. Do something like:
echo "[$count]"
to make sure it doesn't have a newline at the end or, better yet, get a hex dump of it in case it holds strange characters, like Windows line endings:
echo -n $count | od -xcb
The fact that you're seeing:
: integer expression expected30: [: 2
with the : back at the start of the line, rather than the more usual:
-bash: [: XX: integer expression expected
tends to indicate the presence of a carriage return in there, which might be from deployment.conf having those Windows line endings (\r\n rather than the UNIXy \n).
The hex dump should make that obvious, at which point you need to go and clean up your configuration file.
Ref : http://linux.die.net/man/1/bash
-eq, -ne, -lt, -le, -gt, or -ge
These are arithmetic binary operators in bash scripting.
I have checked your code,
deployment.conf
# CONF FILE
EAR_COUNT=5
testArithmetic.sh
#!/bin/bash
. ./deployment.conf
count=$EAR_COUNT;
echo "count : $count";
if [ $count -gt 0 ]; then
echo "Test"
fi
running the above script evaluates to numeric comparison for fine. Share us your conf file contents, if you are facing any issues. If you are including the conf file in your script file, note the conf file must have valid BASH assignments, which means, there should be no space before and after '=' sign.
Also, you have mentioned WAR_COUNT=3 in conf part and used 'count=$EAR_COUNT;' in script part. Please check this too.
Most likely you have some non-integer character like \r in your EAR_COUNT variable. Strip all non-digits while assigning to count like this:
count=${EAR_COUNT//[^[:digit:]]/}
echo "count : $count";
if [[ $count -gt 0 ]]; then
echo "Test"
fi

What is the gt for here? "if [ $VARIABLE -gt 0 ]; then"

What does the -gt mean here:
if [ $CATEGORIZE -gt 0 ]; then
This is part of a bash script I'm working with.
Also, where can I find a list of "flags" that go in there so I can have for reference in the future?
-gt is an arithmetic test that denotes greater than.
Your condition checks if the variable CATEGORIZE is greater than zero.
Quoting from help test (the [ is a command known as test; help is a shell builtin that provides help on shell builtins):
arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.
-eq: Equal
-ne: Not equal
-lt: Less than
-le: Less than or equal to
-gt: Greater than
-ge: Greater than or equal to
You could also express the condition in an arithmetic context1 by saying:
if ((CATEGORIZE > 0)); then
instead of
if [ $CATEGORIZE -gt 0 ]; then
1 Quoting from help '((':
(( ... )): (( expression ))
Evaluate arithmetic expression.
The EXPRESSION is evaluated according to the rules for arithmetic
evaluation. Equivalent to "let EXPRESSION".
Exit Status:
Returns 1 if EXPRESSION evaluates to 0; returns 0 otherwise.
-gt means "greater than", compared arithmetically
[ is (peculiarly) an alias of test (with a mandatory last argument of ], to make it look like a pair of brackets).
bash has its own "builtin" version of [/test, so any bash reference (e.g man bash, info bash, or http://www.gnu.org/software/bash/manual/) will document that, or man [/man test should give you the documentation for the standard standalone version.
Specifically, this page gives an overview of the command, as implemented by bash, and this page lists the available operators.
As well as arithmetic and string tests, you may come across the -e test, for "file exists", as in [ -e /hard/coded/path/$variable_filename ]
bash also includes a slightly extended version, in the form of [[ ... ]].

Compare integer in bash, unary operator expected

The following code gives
[: -ge: unary operator expected
when
i=0
if [ $i -ge 2 ]
then
#some code
fi
why?
Your problem arises from the fact that $i has a blank value when your statement fails. Always quote your variables when performing comparisons if there is the slightest chance that one of them may be empty, e.g.:
if [ "$i" -ge 2 ] ; then
...
fi
This is because of how the shell treats variables. Assume the original example,
if [ $i -ge 2 ] ; then ...
The first thing that the shell does when executing that particular line of code is substitute the value of $i, just like your favorite editor's search & replace function would. So assume that $i is empty or, even more illustrative, assume that $i is a bunch of spaces! The shell will replace $i as follows:
if [ -ge 2 ] ; then ...
Now that variable substitutions are done, the shell proceeds with the comparison and.... fails because it cannot see anything intelligible to the left of -gt. However, quoting $i:
if [ "$i" -ge 2 ] ; then ...
becomes:
if [ " " -ge 2 ] ; then ...
The shell now sees the double-quotes, and knows that you are actually comparing four blanks to 2 and will skip the if.
You also have the option of specifying a default value for $i if $i is blank, as follows:
if [ "${i:-0}" -ge 2 ] ; then ...
This will substitute the value 0 instead of $i is $i is undefined. I still maintain the quotes because, again, if $i is a bunch of blanks then it does not count as undefined, it will not be replaced with 0, and you will run into the problem once again.
Please read this when you have the time. The shell is treated like a black box by many, but it operates with very few and very simple rules - once you are aware of what those rules are (one of them being how variables work in the shell, as explained above) the shell will have no more secrets for you.
Judging from the error message the value of i was the empty string when you executed it, not 0.
I need to add my 5 cents. I see everybody use [ or [[, but it worth to mention that they are not part of if syntax.
For arithmetic comparisons, use ((...)) instead.
((...)) is an arithmetic command, which returns an exit status of 0 if
the expression is nonzero, or 1 if the expression is zero. Also used
as a synonym for "let", if side effects (assignments) are needed.
See: ArithmeticExpression
Your piece of script works just great. Are you sure you are not assigning anything else before the if to "i"?
A common mistake is also not to leave a space after and before the square brackets.

Resources