While learning a bit about bash, I come to see four types of ways of working with if statements:
Single Parenthesis - ( ... )
Double Parenthesis - (( ... ))
Single Square Bracket - [ ... ]
Double Square Brackets - [[ ... ]]
What is the difference between Parenthesis and Square Brackets in bash.
The tests you had listed :
Single Parenthesis - ( ... ) is creating a subshell
Double Parenthesis - (( ... )) is for arithmetic operation
Single Square Bracket - [ ... ] is the syntax for the POSIX test
Double Square Brackets - [[ ... ]] is the syntax for bash conditional expressions (similar to test but more powerful)
are not exhaustive, you can use boolean logic
if command; then ...
too, because the commands have exit status. In bash, 0 is true and > 0 is false.
You can see the exit status like this :
command
echo $?
See :
http://wiki.bash-hackers.org/syntax/basicgrammar
http://wiki.bash-hackers.org/syntax/arith_expr
http://mywiki.wooledge.org/BashGuide/TestsAndConditionals
The shell itself only runs the command and evaluates its exit code. A zero exit code signifies success; all other values indicate failure.
if command; then
: things to do if the exit code from command was 0
else
: things to do if it was not 0
fi
while command; do
: things to do if the exit code was 0
done
The command [ (aka test) is very commonly used in conditionals, because the original Bourne shell lacked built-in operators to check if a string was empty or a file existed. Modern shells have this command built in, and many shells have an extended and modernized version [[, but this is not properly portable to POSIX sh and should thus be avoided for portable scripts. This related question explains the differences between the two in more detail.
The notation (( ... )) introduces an arithmetic context. Again, this was something which was not part of the original Bourne shell (it had a dedicated external tool expr for these things) but modern shells have this built in. The result code of an arithmetic expression is 0 if the result of the arithmetic evaluation was not 0 (or an error).
The notation ( command ) creates a subshell and evaluates command in that. There are situations where this is actually necessary and useful, but if you are only just learning the syntax, you are unlikely to need this.
... In fact, in the majority of scripts I have seen this used in a conditional, it was clearly unnecessary.
Another antipattern to look out for is
command
if [ $? = 0 ]; then
: things
fi
You should almost never need to examine $? explicitly, and in particular, comparing it to zero is something if and while specifically do for you behind the scenes. This should simply be refactored to
if command; then
: ...
Related
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. :)
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.
Recently I've got confused by the following situation.
What is the difference between two if usage:
Case 1
amount=10
if [[ $amount -eq 10 ]]
then
echo "something"
fi
script output:
$ ./1.sh
something
Case 2
if [[ amount -eq 10 ]]
This also works like this (note that the variable name doesn't contain the $).
So the question is how does it work even without dollar sign in the variable name.
P.S. I'm using a POSIX shell on HP-UX.
man bash
ARITHMETIC EVALUATION
...
Shell variables are allowed as operands; parameter expansion is per‐
formed before the expression is evaluated. Within an expression,
shell variables may also be referenced by name without using the
parameter expansion syntax.
In this context shell does not expect anything but numerics, so it expands strings as variables. That makes sense to me.
How can you check which words is first alphabetically between two words?
For example in the code
#!/bin/bash
var1="apple"
var2="bye"
if [ $var1 \> $var2 ]
then
echo $var1
else
echo $var2
fi
I want it to print apple, since apple comes before bye alphabetically, but it isnt working as intended. What am I doing wrong?
What you need to do to solve the immediate problem is reverse the sense of your statement, since the "less than" operator is < rather than >.
Doing so will get it working correctly:
if [ $var1 \< $var2 ]
Alternatively, you can use the [[ variant which doesn't require the escaping:
if [[ $var1 < $var2 ]]
I prefer the latter because:
it looks nicer; and
the [[ variant is much more expressive and powerful.
You'll want to use the [[ ]] construct and print out the one that is less than the other
#!/bin/bash
var1="apple"
var2="bye"
if [[ $var1 < $var2 ]]; then
echo $var1
else
echo $var2
fi
It seems that you are suffering from two misconceptions about (bash) shell scripting.
First the line if [ $var1 > $var2 ] then echo ... is syntactically wrong and you should really paste your example commands or code rather than trying to re-type them from memory. It would be fine if you'd said if [[ "$var1" > "$var2" ]]; then ... or if [ "$var" \> "$var2" ]; then ....
Note that [[ is a bash specific conditional expression while [ (single bracket) introduces the shell built-in implementation of the /usr/bin/[ (alias for /usr/bin/test) command.
The old [ (test) command has much more constrained features than the [[ support in bash. It only support -lt, -eq ... and other integer comparisons and the various file and value length (-z' and-n) and other tests. It has no support for lexical/string or pattern (regex nor glob) comparisons. Thebashbuilt-in for[supports a number of the[[` extensions but, as shown some of them have to be explicitly escaped from the legacy parsing.
Also note that it's dangerous to use bar dereferences of $var (vs "$var" with the quotes). If the value assigned to var has any embedded spaces or various other operators which might be conflated with the switches to the test command.
Also you need the ; to separate the if command from the then clause.
Trying to write shell scripts as though shell were a normal programming language will lead you to quite a bit of this sort of confusion. Shells, such as bash have relatively little syntax and built-in functionality and most of that is glue around running commands. In early versions of UNIX the shell didn't have any built-in tests and relied entirely on the external test command. Over time more and more functionality was built-in the shell, often through aliases to those old commands (/usr/bin/[' is literally a link to the/usr/bin/testcommand and the shell built-ins for[andtest` are internal aliases to one another and implemented as (mostly?) compatible with the older (and still extant) external binaries.
Similarly all arithmetic operations in the early Bourne shells were done using external commands such as /usr/bin/expr. Korn shell and Bash added $((...)) and let and ((...)) expressions
for evaluating arithmetic expressions from within the shell without external command support.
Other examples relate to the support for arrays (declare) and parameter expansion ${var#...}
various other forms.
It's generally best to avoid most such features, or use them sparingly, as the resulting scripts because progressively less portable as you use them ... and the syntactic machinations rapidly overwhelm the code. At some point it's best to use Perl, Python, Ruby or some general purpose programming/scripting language for performing the general programming work and use shell for the purposes to which it was designed ... as glue around external commands, for marshaling data and variables into and out of those external commands/processes.
I have seen scripts using the test command and [ ] or [[ ]]. But when do we need to use /usr/bin/test and (( ))?
Are there any occasions when we need to go for the latter?
Regards,
John
To answer your question:
you want to use /usr/bin/test when you want to test something but not in a shell (for example find ... -exec test ...)
you want to use (( )) when you have an arithmetic expression to solve, AND you are using bash, because (( )) is bash specific.
Now for some background:
The command /usr/bin/test is required by the POSIX standard. POSIX also requires that [ is defined as an alias for test. The only difference between test and [ is that [ requires the final parameter to be a ].
Since test is used so frequently in shell scripts, most shells have a builtin version of test (and [). The advantage of a builtin is that it avoids context switches. Which, depending how you use test in your script, can be a measurable performance advantage.
I think it is safe to assume that under most circumstances it doesn't matter whether you use the system test or the shell's builtin test. But if you want to use test in a find -exec situation then of course you have to use the system test because find cannot use the shell test.
(( )) and [[ ]] were introduced by bash (and perhaps some other shells) as syntactic sugar. (( )) evaluates arithmetic expressions, while [[ ]] evaluates logical expressions. Both allow you to write the expressions in a "more natural syntax".
The decision to use [[ or [ depends on whether you want to use the "more natural syntax", and, since sh does not support [[, whether you want to depend on bash.
The decision to use (( )) depends on whether you need arithmetic expressions, and again, since sh does not support (( )), whether you want to depend on bash. The POSIX alternative to (( )) is $(( )). Note that there are some subtle differences in the behaviour.
The following links explain these topics in great detail:
http://mywiki.wooledge.org/BashFAQ/031 (difference between test, [ and [[)
http://mywiki.wooledge.org/ArithmeticExpression (let, (( )) and $(( )))
http://www.ibm.com/developerworks/library/l-bash-test/index.html (all of the above)
See also:
POSIX definition of test
POSIX definition of $(( ))
Bonus: Some debian developers once argued whether they should use the system test or the shell builtin test, because of some differences in the implementation of the builtin test. If you are interested in details of the differences of the system test and the shell builtin test then you can read the debian developer discussion here: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=267142.
(( )) evaluates an arithmetic expression in bash (see man bash).
[[ ]] evaluates a logic expression in bash (see man bash).
[ ] is the same as test, used to check file types and compare values (see man test).
You use /usr/bin/test when you want things to run more slowly. Modern shells (most shells released since about 1990, probably earlier) have test and its synonym [ as built-in commands. Formally invoking /usr/bin/test would be an act of desparation because the shell has a broken test command and the system standalone is OK - but it would be better to get a fixed shell.
You use (( ... )) to do arithmetic. The old-fashioned alternative was the expr command. That was tricky to use because it required a lot of escaping - it is/was a separate executable, and you had to get lots of shell metacharacters past the shell to expr. Hence:
x=$(expr $y '*' $z)
instead of
((x = y * z))
You don't even have to decorate the variables with $ in (( ... )).