Why does this string comparison if statement fail to work - bash

I expect the following code to print only one x, but it always gives two:
#!/bin/sh
read i
if [[ $i!=1 ]];then
echo x
fi
if [[ $i==1 ]];then
echo x
fi
Why is this happening?

First of all, your script is invalid in POSIX sh, since [[ is undefined in sh.
In bash, on the other hand, you're missing spaces around comparison operators != and ==. That's why the expression inside [[ ]] is treated as a non-zero length string, which is truthy. Hence, echo is printed twice.
As suggested so many times on StackOverflow, it's always good to run your shell scripts through shellcheck (available as command line tool also), which will help you catch and explain many of such errors.

Related

How to use variables in bash conditional expression?

I'd like to have some explanation on how to best use variables in bash conditional expressions [[...]].
I usually write if statement in this way:
var=1;
# some code...
if [[ $var -eq 1 ]]; then echo "ok"; else echo "fail"; fi
and this return ok as I expected.
Now I saw in some script the same similar statement like:
var=1;
# some code...
if [[ var -eq 1 ]]; then echo "ok"; else echo "fail"; fi
The only difference is the missing parameter expansion character $ in the conditional expression [[...]].
I actually expected this statement to give an error, but this syntax is accepted and returns the ok string.
I tested this statement using bash (GNU bash, version 4.3.46), zsh (5.1.1), ksh (93u+ 2012-08-01) and busybox ash (BusyBox v1.23.2).
I only get an error with busybox shell:
ash: var: bad number
I saw in the bash man page, in the ARITHMETIC EVALUATION paragraph, that:
Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax
But I didn't find anything special related to parameter expansion in the CONDITIONAL EXPRESSIONS paragraph.
So, should conditional expression contain $ when referring to variable or not? and why?
The trigger here is -eq; since it is defined to perform integer comparison, its operands are evaluated in an arithmetic context. This isn't explicitly documented, though.
You should use the $, though. [[ is an extension, so there is no guarantee that it will behave identically in every shell that defines such a construct. In fact, I wouldn't even assume that [[ var -eq 3 ]] will continue to behave this way in future versions of the same shell. (( var == 3 )) is, though, documented to perform expansion of var since you are in a explicit arithmetic context.
Check the bash man page's sections on Compound Commands. In particular, the following:
((expression))
The expression is evaluated according to the rules described below
under ARITHMETIC EVALUATION. If the value of the expression is non-zero,
the return status is 0; otherwise the return status is 1. This is exactly
equivalent to `let "expression"`.
[[ expression ]]
Return a status of 0 or 1 depending on the evaluation of the conditional
expression expression. Expressions are composed of the primaries
described below under CONDITIONAL EXPRESSIONS.
If you are evaluating things that require arithmetic, use arithmetic evaluation, and check the CONDITIONAL EXPRESSIONS section for the various things you can do with [[ ... ]]. Conditions in double-square-brackets can evaluate both strings and integers, and sometimes those work the same way .. sometimes not.
From the bash man page, under CONDITIONAL EXPRESSIONS:
string1 == string2
string1 = string2
True if the strings are equal. = should be used with the test command for POSIX conformance. When used with the [[ command, this performs pattern
matching as described above (Compound Commands).
...
arg1 OP arg2
OP is one of -eq, -ne, -lt, -le, -gt, or -ge. These arithmetic binary operators return true if arg1 is equal to, not equal to, less than, less than
or equal to, greater than, or greater than or equal to arg2, respectively. Arg1 and arg2 may be positive or negative integers.
Obviously, the string "1" is the string "1", so if n=1, and you compare $n against the string "1", you'll get a hit. But you should know what you're doing, and that this is not a numeric comparison. And similarly, < and > are NOT numeric comparisons, they are string comparisons. So while "1" < "2", you may be surprised that "11" < "2" as well.
That said, bash is forgiving about what kind of conditions you ask it to evaluate:
bash-4.4$ n=1
bash-4.4$ [[ n -eq 1 ]] && echo yes
yes
bash-4.4$ [[ $n -eq 1 ]] && echo yes
yes
bash-4.4$ (( n == 1 )) && echo yes
yes
bash-4.4$ (( n = 2 )) && echo yes
yes
bash-4.4$ echo "$n"
2
The first one works because n can't be anything but a variable in this context, so bash treats it as such. But you shouldn't rely on this behaviour. Use dollar signs for variables in conditional expressions, and stick with the bash mantra, "always quote your variables".
Inside a double-square-bracket expression in bash, you should use the arithmetic binary operators if you intend your comparison to be of integers.
Note that your busybox build appears to be using ash, which is NOT bash. Ash is the "Almquist shell", an older POSIX shell than bash, written in the late 1980s. Ash is the basis for /bin/sh in FreeBSD, in addition to being preferred often over bash in embedded systems (hence busybox) due to its smaller size.
You might want to consider writing POSIX shell scripts instead of Bash shell scripts. This will mean simplifying some things, as well as jumping through some hoops for others. POSIX does not include double-square-bracket expressions, but it does allow things like [ "$n" -eq 1 ] or [ $(( n + 1 )) -eq 2 ]. And it'll work in busybox. :)

Bash glob in string comparison

The other day I was struggling with an if statement. Turns our my variable had a white space at the beginning. So I tried to conquer this with the following code but I am having no luck.
if [ "$COMMAND_WAIT" == "*REBOOT" ]; then
sudo /etc/kca/scripts/reboot.sh
echo "REBOOTING"
fi
Should I be able to wildcard this statement or is there another way around this?
The following should work. It uses [[ instead of [, and no quotes around the pattern.
if [[ "$COMMAND_WAIT" == *REBOOT ]]; then
sudo /etc/kca/scripts/reboot.sh
echo "REBOOTING"
fi
[[ expression ]] is a compound expression, with special rules regarding expansions and quoting. In contrast, [ is a builtin command, i.e. *REBOOT will be expanded as a pathname. In most cases, it's easier to use [[ instead of [.

Kornshell If statement not working

So I'm new to kornshell and i can't get my if statement to work. This what I have in my file
if $1 = "Y"
then
echo "I am here";
fi
And when I execute the file this is the error that I get:
test.ksh[1]: Y: not found [No such file or directory]
What am I doing wrong?
if must be followed by a command; it tests whether the command terminates successfully or not.
You can use the command test or the equivalent command [, or the ksh builtin [[:
if test "$1" = Y
or
if [ "$1" = Y ]
or
if [[ $1 == Y ]]
The above commands succeed if the condition indicated by its arguments is true. man test should provide you with a list of valid conditions. Remember to quote your variable substitutions for the test and [ commands; if not, undefined variables will be substituted with nothing (i.e. they will be deleted) which usually an error report.
If you don't need strict Posix compatibility, use the ksh built-in [[, which is also available in bash. It is more convenient because it does not word-split its arguments, so variable expansions don't need to be quoted. Note that with [[, the right-hand side of the comparison == is a pattern ("glob"), unless it is quoted, so you might need quotes on the right-hand side if you need a literal equality check.

Bash [[ tests, quoting variables

I want to decide whether to always omit quotes for variables appearing withing a Bash [[ test. I interpret the man page to say that it is permissible to do so without any loss of correctness.
I devised this simplistic "test" to verify my thinking and the "expected behaviour" but it may prove absolutely nothing, take a look at it:
x='1 == 2 &&'
if [[ $x == '1 == 2 &&' ]]; then
echo yes
else
echo no
fi
Note I am not writing this as such:
x='1 == 2 &&'
if [[ "$x" == '1 == 2 &&' ]]; then
echo yes
else
echo no
fi
which so far has always been my style, for consistency if nothing else.
Is is safe to switch my coding convention to always omit quotes for variables appearing within [[ tests?
I am trying to learn Bash and I am trying to do so picking up good habits, good style and correctness..
The key thing to remember is that quotes and escaping within pattern matching contexts always cause their contents to become literal. Quoting on the left hand side of an == within [[ is never necessary, only the right side is interpreted as a pattern. Quoting on the right hand side is necessary if you want a literal match and to avoid interpretation of pattern metacharacters within the variable.
In other words, [ "$x" = "$x" ] and [[ $x == "$x" ]] are mostly equivalent, and of course in Bash the latter should be preferred.
One quick tip: think of the operators of the [[ ]] compound comand as being the same grammar-wise as other control operators such as elif, do, ;;, and ;;& (though technically in the manual they're in their own category). They're really delimiters of sections of a compound command, which is how they achieve seemingly magical properties like the ability to short-circuit expansions. This should help to clarify a lot of the behavior of [[, and why it's distinct from e.g. the arithmetic operators, which are not like that.
More examples: http://mywiki.wooledge.org/BashFAQ/031#Theory
No. You should not get in the habit of always omitting quotes, even if they appear within [[ tests. Bash is famous for burning people for leaving off quotes :-)
In bash the [[ ]] should always evaluate as an expression, so the script will continue to function. The risk is that a logic error may pop up unnoticed. In all cases that I can think of off the top of my head it would be fine. However, quotes allow you to be specific about what you want, and are also self-documenting in addition to being safer.
Consider this expression:
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
It would still work without the quotes because it is in between [[ ]], but the quotes are clarifying and do not cause any issues.
At any rate, this is my opinion as a guy who has received a royal hosing at the hand of Bash because I failed to put " " around something that needed them :'(
My bash hacking friend once said, "use quotes liberally in Bash." That advice has served me well.

What's the different between "[]" and "[[]]" [duplicate]

This question already has answers here:
Are double square brackets [[ ]] preferable over single square brackets [ ] in Bash?
(10 answers)
Closed 4 years ago.
I looked at bash man page and the [[ says it uses Conditional Expressions. Then I looked at Conditional Expressions section and it lists the same operators as test (and [).
So I wonder, what is the difference between [ and [[ in Bash?
[[ is bash's improvement to the [ command. It has several enhancements that make it a better choice if you write scripts that target bash. My favorites are:
It is a syntactical feature of the shell, so it has some special behavior that [ doesn't have. You no longer have to quote variables like mad because [[ handles empty strings and strings with whitespace more intuitively. For example, with [ you have to write
if [ -f "$file" ]
to correctly handle empty strings or file names with spaces in them. With [[ the quotes are unnecessary:
if [[ -f $file ]]
Because it is a syntactical feature, it lets you use && and || operators for boolean tests and < and > for string comparisons. [ cannot do this because it is a regular command and &&, ||, <, and > are not passed to regular commands as command-line arguments.
It has a wonderful =~ operator for doing regular expression matches. With [ you might write
if [ "$answer" = y -o "$answer" = yes ]
With [[ you can write this as
if [[ $answer =~ ^y(es)?$ ]]
It even lets you access the captured groups which it stores in BASH_REMATCH. For instance, ${BASH_REMATCH[1]} would be "es" if you typed a full "yes" above.
You get pattern matching aka globbing for free. Maybe you're less strict about how to type yes. Maybe you're okay if the user types y-anything. Got you covered:
if [[ $ANSWER = y* ]]
Keep in mind that it is a bash extension, so if you are writing sh-compatible scripts then you need to stick with [. Make sure you have the #!/bin/bash shebang line for your script if you use double brackets.
See also
Bash FAQ - "What is the difference between test, [ and [[ ?"
Bash Practices - Bash Tests
Server Fault - What is the difference between double and single brackets in bash?
[ is the same as the test builtin, and works like the test binary (man test)
works about the same as [ in all the other sh-based shells in many UNIX-like environments
only supports a single condition. Multiple tests with the bash && and || operators must be in separate brackets.
doesn't natively support a 'not' operator. To invert a condition, use a ! outside the first bracket to use the shell's facility for inverting command return values.
== and != are literal string comparisons
[[ is a bash
is bash-specific, though others shells may have implemented similar constructs. Don't expect it in an old-school UNIX sh.
== and != apply bash pattern matching rules, see "Pattern Matching" in man bash
has a =~ regex match operator
allows use of parentheses and the !, &&, and || logical operators within the brackets to combine subexpressions
Aside from that, they're pretty similar -- most individual tests work identically between them, things only get interesting when you need to combine different tests with logical AND/OR/NOT operations.
The most important difference will be the clarity of your code. Yes, yes, what's been said above is true, but [[ ]] brings your code in line with what you would expect in high level languages, especially in regards to AND (&&), OR (||), and NOT (!) operators. Thus, when you move between systems and languages you will be able to interpret script faster which makes your life easier. Get the nitty gritty from a good UNIX/Linux reference. You may find some of the nitty gritty to be useful in certain circumstances, but you will always appreciate clear code! Which script fragment would you rather read? Even out of context, the first choice is easier to read and understand.
if [[ -d $newDir && -n $(echo $newDir | grep "^${webRootParent}") && -n $(echo $newDir | grep '/$') ]]; then ...
or
if [ -d "$newDir" -a -n "$(echo "$newDir" | grep "^${webRootParent}")" -a -n "$(echo "$newDir" | grep '/$')" ]; then ...
In bash, contrary to [, [[ prevents word splitting of variable values.

Resources