Project 81. Write Complex Bash Conditions"How does Bash interpret conditional expressions?" This project looks at the many forms of conditional expression supported by Bash. It explains the differences of the forms and compares them with one another. It also presents some handy tricks and gives tips on how to avoid syntax errors and malformed conditions. Learn More
Understand Bash ConditionsBash supports conditional expressions that are used in conditional statements such as if, while, and until. Here's an example in which we test whether 5 is less than 7 (we use -lt to mean less than). The condition is enclosed in [...] and evaluates to true or false. Ideally, Bash will find truth in such a condition. $ if [ 5 -lt 7 ]; then echo "yes"; else echo "no"; fi yes Now let's examine this simple expression in more detail to discover how Bash interprets it, and explore the alternative forms of conditional expression offered by Bash. There is more to Bash conditional expressions than is at first apparent. Let's look at how Bash interprets a conditional expression. This is key to understanding the different forms and being able to make the most of them. When interpreting a conditional statement such as if, Bash does not expect to see a Boolean value (TRUE or FALSE), as other languages do, such as C and PHP. Rather, Bash expects to see an executable command. The syntax is effectively if command; then... Within such a command line, Bash executes the command that follows if and replaces it with whatever value the command returns. A return value of 0 is interpreted as TRUE; any other return value is interpreted as FALSE. The [ CommandIn our example statement, you might well ask about the whereabouts of the command Bash requires following if. The answer is a little surprising: Bracket ([) is actually a built-in Bash command. When interpreting a conditional statement such as if [ 5 -lt 7 ]; then... Tip
Bash first executes the bracket command, passing it the four parameters that form the remainder of the statement: 5, -lt, 7, and ]. (The statement is terminated by a semicolon.) The bracket command (not Bash command-line interpretation) evaluates the conditional expression and returns 0 if the statement is true (as it is in this case) and 1 if it is false. Learn More
In our example, then, after it has executed the bracket command, Bash effectively sees the statement if 0; then... and interprets it as if TRUE; then.... We check the credentials of bracket with the type command. $ type [ [ is a shell builtin Equivalent to [ is the test command. The two are identical except that test does not expect to see a closing bracket. $ if test 5 -lt 4; then echo "yes"; else echo "no"; fi no $ type test test is a shell builtin To discover all the conditional operators supported by bracket and test, consult Bash's built-in help command by typing $ help test Learn More
Several examples are given in the next section. Here's a neat trick. A conditional statement may be given any command, not just [ or test. We could test whether two files differ by directly testing the return value from the diff command. $ if diff eg1.txt eg2.txt &> /dev/null > then echo "Same"; else echo "Different"; fi Same Most commands return 0 (trUE) for success or yes and 1 (FALSE) for failure or no. In the diff example, we took the precaution of throwing away all errors and other output by using the redirection &>/dev/null to prevent the shell script from writing unwanted text to the Terminal screen when it executes. Tip
Example ConditionalsThe bracket command has a number of primaries you can use to test file attributes, such as whether a file exists. $ if [ -e no-file ]; then echo "Exists"; ¬ else echo "No such file"; fi No such file or whether you own a particular file. $ if [ -O eg1.txt ]; then echo "It's mine"; fi It's mine Bracket can compare strings for less than, greater than, equality, inequality, and emptiness. The next two examples demonstrate tests for equality and emptiness. The -z primary returns TRUE if the length of the string that follows is 0 (the string is empty). $ ans="" $ if [ "$ans" = "yes" ] > then echo "You agree"; else echo "You disagree"; fi You disagree $ if [ -z "$ans" ]; then echo "You didn't reply"; fi You didn't reply Integer evaluation is performed as demonstrated in previous examples, using -eq for equality, -ne for inequality, and so on. Type help test for more information. Complex ConditionsYou may specify more complex conditions by using AND, represented by -a; OR, represented by -o; and NOT, represented by !. We can test whether both the variables ans and default are empty by using the following complex condition. $ if [ -z "$ans" -a -z "$default" ] > then echo "I don't know what you want"; fi Don't omit the spaces between operators and operands. In the next example, we have omitted the spaces around the = sign. $ allow=""; user="" $ if [ "$allow"="yes" -o "$user"="root" ]; ¬ then echo "OK"; fi OK Omitting the spaces makes the conditional expression appear to be [ "non-null-string" -o "non-null-string" ] This is how it should look and evaluate. $ if [ "$allow" = "yes" -o "$user" = "root" ]; ¬ then echo "OK"; fi $ We form expressions that are more complex by employing parentheses to ensure that evaluation occurs in the correct order. Our first attempt does not work. $ ans="yes"; allow="no"; user="root" $ if [ "$ans" = "yes" -a ¬ ( "$allow" = "yes" -o "$user" = "root") ] -bash: syntax error near unexpected token `(' The syntax error is reported because the parentheses are parameters to the bracket command and must be escaped from the shell, as demonstrated in our next attempt. $ if [ "$ans" = "yes" -a ¬ \("$allow" = "yes" -o "$user" = "root" \) ] > then echo "OK"; fi OK Tip
Bash Boolean OperatorsCompare the next two commands. $ if [ "$allow" = "yes" -o "$user" = "root" ]; ¬ then echo "OK"; fi $ if [ "$allow" = "yes" ] || [ "$user" = "root" ]; ¬ then echo "OK"; fi The difference between the two statements is that in the first example, the built-in bracket command evaluates the whole expression. In the second example, we have two separated bracket commands, and it's Bash that performs the OR operation, using its own || operator. The two commands are functionally equivalent; which you choose is a matter of personal preference. Bash uses a more friendly and C languagelike syntax. It provides OR (||), AND (&&), and NOT (!) operators.
We can employ Bash operators outside a conditional statement. For example: $ command1 && command2 In such a command, command2 is executed if, and only if, command1 returns TRUE. This technique works because Bash does not evaluate the second part of an AND statement if the first part if FALSE; the result can only ever be FALSE. This behavior is known as short-circuiting. Similarly, we could specify $ command1 || command2 In this example, command2 is executed if, and only if, command1 returns FALSE. As a practical example, think of what happens if we type the following command line in a directory where no subdirectory named fred exists. $ cd fred; ls -bash: cd: fred: No such file or directory Desktop Library Music Public ... Tip
Command cd returns an error, but ls executes anyway, listing the current directory. To avoid executing the ls command when the cd command fails, we use the following trick, which relies on the fact that cd returns TRUE when it succeeds and FALSE when it fails. $ cd fred && ls -bash: cd: fred: No such file or directory $ Use the [[ KeywordBash provides a relatively new way of specifying a conditional expression, called an extended conditional expression. It uses the syntax [[...]] instead of [...] and is compatible with the older form. It is, in fact, a keyword like if and while, not a command like [ and cd, and suffers fewer limitations. It also uses the more friendly syntax && and || for AND and OR. We may type a conditional expression such as $ if [[ "$allow" = "yes" || "$user" = "root" ]]; ¬ then echo "OK"; fi Extended conditional expressions also spare you the trouble of escaping any parentheses they contain. $ if [[ ("$allow" = "yes") || ("$user" = "root") ]]; ¬ then echo "OK"; fi Note
Beware, however, that bare numbers within extended conditionals are treated as stringstext sequences without numerical value. You might be tempted to use this expression in the belief that Bash is employing integer arithmetic when evaluating the expression $ if [[ (3 < 5) ]]; then echo "OK"; fi OK Tip
But it's not, as we can see by this example. $ if [[ (3 < 15) ]]; then echo "OK"; fi $ Use Bash Integer ConditionsWhen writing conditional expressions that involve integer values, use the Bash ((...)) construct. Like [[...]], it uses C languagelike syntax. Although [[...]] is for general conditions, ((...)) operates only on integer values and variables. Here's an example. $ v1=3; v2=2 $ if (($v1 < $v2)); then echo "yes"; else echo "no"; fi no You may omit the $ normally required for variable expansion. $ if ((v1 < v2)); then echo "yes"; else echo "no"; fi no Tip
The Bash ((...)) construct provides a more friendly syntax by employing && and ||, unescaped parentheses, and < in place of -lt. We could write $ if (((a < b) && (b < c))); then... or $ while ((length!=0)) |