Softpanorama
May the source be with you, but remember the KISS principle ;-)

Contents Bulletin Scripting in shell and Perl Network troubleshooting History Humor

Comparison operators in shell

News

See also

Recommended  Links

Arithmetic expressions Double square bracket conditionals Single square bracket conditionals
Compound comparisons Integer comparison operators String comparison operators File test operators Quoting caveats Macro substitutions caveats
if statements in shell Loops in Shell Case statement in shell Shell History Humor Etc

In general, implementation of shell arithmetic expressions and conditional operators are the most obscure areas of shell and are implemented considerably worse (or more dirty, if you wish) that in any other important programming languages in existence.  One reason for this is that they are implemented as afterthought by people who tried to correct grave flaws in the design of Bourne shell. That will always be a dark spot of the biography of Steve Bourne.  Bourne shell was the default shell of Unix version 7 (1977). The fact that before implementing shell he used to work on Algol-68 compiler makes this situation tragicomical: you can't imagine any person who after working on Algol-68 can made so stupid mistakes in constructing the shell language. Especially when in the other room of the same building people created (much superior and cleaner) AWK. 

Those grave flaws cause a lot pain to students and I can do nothing but try to explain this construct the best that I can. Unfortunately I an not God and pain remains. You need to be very careful in your first scripts not to get into some Gotchas. The best way is to use ready-made script with common function as a prototype and gradually modify it.

Key ideas of Unix shell are more then 40 years old. It was create at the same time as REXX (1979) and   Icon (1977). Also  if you look at AWK which was created the same years as shell you can understand the difference in the caliber of people involved. Not only Bourne shell adopted strange, counterintuitive and different from every other language notation for comparisons, each different flavor of shell implements them differently.  Even worse they have  they have multiple Gotchas.  Absence of lexical scanner (and lexical level) in Bourne shell creates real mess.

Historically shell notation for conditionals was developed in three stages: first there was single square bracket construct [...] (single square bracket conditionals or test construct), then due to compensate weaknesses of this construct [[...]] (double square bracket construct) was introduced in ksh 88. And then later double round bracket construct was introduced in ksh93 to compensate for absurdly bad implementation of integer comparisons in both [..] and [[...]] constructs. This solution from ksh93 migrated to bash (starting from version 2.05b) and other shells. 

Shell casts operators into strings or numbers depending of the type of operation used (implicit type conversion). So the second way to classify and study conditional expressions is to type based classification. I think that it is the best for learning the intricacies of shell conditional operators and such an approach can provide a useful framework that helps to avoid most Gotchas.  It is adopted below.  From this point of view in modern shells such as bash we have:

  1. Integer comparison operators (new style ((...)) notation and two old style notations [[...]] or [...] )
  2. String comparison operators ( Double square bracket condition )
  3. File test operators ( single square bracket conditions -- oldest and almost obsolete by now construct).

One note about bash: as bash is a derivative of Borne shell, it inherited all the mess that was emanating from it so in some respect it is even worse then ksh93. The developers has neither clue,  no the courage to create a better shell and slide into the easy road of reimplementing all the quirks of Borne shell plus some. The following three rules can help to avoid at least some Gotchas:

Among most annoying Gotchas related to comparison operators one can mention Macrosubstitutions caveats and Quoting caveats.  There is also less frequent "Implicit type conversions caveat" when you expect the operand to be numeric but it is a sting or vice versa. Those generally should be classified as programming mistakes.

Tips:

Integer Comparisons and their Gotchas

You can compare integers is shell in three different ways: using  new style ((...)) notation and two old style notations [[...]] or [...]. Only new style ((..)) notation can be recommended for this purpose.

Here is the full annotated list of available possibilities:

  1. Double round brackets conditionals This is a newer construct and it is the best of three available. They are covered in in detail in Arithmetic Expressions in BASH
  2. Double square bracket conditionals This is older construct that currently makes sense only for string comparisons.
  3. Single square brackets conditionals. This is the oldest construct that does not make sense anymore for integer comparisons.

In its turn Double round brackets conditionals can be classified into two types:

To preserve sanity it is better to use only "double round bracket comparison" without using dollar sign prefix before variables. This flavor of the  "double round bracket" construct  is supported in bash and all implementations of ksh in major Unixes so it is perfectly portable.   Here is short summary of operators for "double round bracket comparison"

In addition, omitting "$" sign before variable helps to avoid some stupid legacy behaviors that plague shell due to its way too long historical development path.

Tip: Never use old-style shell comparison constructs (single square bracket comparison construct and double square bracket construct) unless absolutely necessary. The  "double round bracket" construct is much safer -- it contain far less Gotchas and is portable between bash and ksh88/ksh93.

Dealing with legacy integer comparisons in old scripts

You often need to deal with old scripts that use either [[..]] or even older [..] construct for integer comparisons. With [..] situation is pretty simple -- you can convert it to [[..]]. With [[..]] situation is more complex and in large scripts it is too much trouble to convert those construct into more modern ((..)) construct.

One unpleasant reality (and probably the most common gotcha) of using legacy integer comparison constructs is that if one of the variable is not initialized it produces syntax error.  That's usually when you need to look and determine "what's wrong" in the legacy scripts. Two classic tricks are used to suppress this nasty macro substitution side effect without going into in-depth analysis of logic ( They can be and often are used simultaneously):

You can also try to initialize offending variables explicitly, but that's usually more involved. Please note that you can't change "legacy integer comparison operators" to normal in most cases, althouth bash 4.2 provides a path for such a change. Here is the list for your reference: 

Dollar sign caveat in double round bracket comparison construct

If variable is used with leading dollar sign in double round bracket comparison construct and it is not initialized it will be substituted as null string like in old constructs and cause syntax errors. That's why we recommended remove dollar sign from all variables in  double round bracket comparison construct.

Here is a small test that illustrated the error (note, variable $a is not initialized)

# cat macrotest.sh
if (( $a == 4 )) ; then
        echo this is what macro substitution is about
fi
Results of execution in bash:
bash -xv macrotest.sh
if (( $a == 4 )) ; then
        echo this is what macro substitution is about
fi
+ ((   == 4  ))
mactotest.sh: line 1: ((: == 4 : syntax error: operand expected (error token is "== 4 ")

If we remove leading $ from variable $a bash behaves pretty reasonably:

# bash -xv mactotest.sh
if (( a == 4 )) ; then
        echo this is what macro substitution is about
fi
+ ((  a == 4  ))
#
If we replace constant 4 with constant zero, then statement will be executed as we would expect from any decent scripting language:
# bash -xv mactotest.sh
if (( a == 0 )) ; then
        echo this is what macro substitution is about
fi
+ ((  a == 0  ))
+ echo this is what macro substitution is about
this is what macro substitution is about
#

You can avoid such a behaviors in older constructs by initializing variable beforehand. Two methods can be used to detect uninitialized variable: 

  1. Conditional with  operator -z (see below):

    [[ -z "$a" ]] && $a=0
  2. Operator ${foo:-bar} either directly in comparison or beforehand:
    if [[ ${a:-0} -gt 0 ]] ...
    or
     $a=${a:-0}

    Here is small test that illustrates behavior of uninitialized variables on double parenthesis comparison

cat "double_round_paren_test.sh"
if [[ -z "$a" ]] ; then
   echo variable '$a' is not defined
fi
if (( a == 0 )) ; then
   echo "this is how to avoid macro substitution pitfall"
fi
Results of execution in ksh  produces an error "a == 0 : bad number" but bash is OK:
export PATH
+ [[ -z  ]]
+ echo variable $a is not defined
variable $a is not defined
+ let  a == 0
double_round_paren_test.sh[4]:  a == 0 : bad number

String comparison operators and their Gotchas

String comparison operations in shell are discussed in Double square bracket conditionals

They suffer from the same macro substitution Gotchas as integer operators, but it is far more common as it accrues not only when string in not initialized but also if its length is zero.  "macro substitution Gotchas" lead to run time syntax error. To avoid it the "double quotes" trick is used:  you need to put variables inside double quotes literal, which converts any type to string. Here is the summary of most typical operators used

You can test the string for being null or uninitialized using -z operator (opposite operator is -n  string is not "null".

 For example:

if [ -z "$myvar" ] ; then
        echo "myvar is not defined"
fi
Sometimes, there are several different ways that a particular comparison can be made. For example, the following two snippets of code function identically:
if [[ "$myvar" -eq 3 ] ; then
    echo "myvar equals 3"
fi


if [[ "$myvar" = "3" ] ; then
    echo "myvar equals 3"
fi

In the above two comparisons do exactly the same thing, but the first uses arithmetic comparison operators, while the second uses string comparison operators.

File test operators

The other kind of operator that can be used in conditional expressions checks if a file has certain properties. There are 21 such operators.

The Gotchas is that not all of them are present in all shells so if you run the same script both in bash and ksh you can get into very unpleasant situation. The typical offending test is -e which is present in bash but not in ksh installed by default in major commercial Unixes. Use -f instead: it is more portable way of achieving the same result in all shells.

Here are some operators provided:

Quoting caveats

Most of the time, while you can omit the use of double quotes surrounding strings and string variables, it's not a good idea. Why? Because your code will work perfectly, unless an environment variable happens to have a space or a tab in it, in which case shell go bananas.  Here's an example of a this Gotchas:

if [ $myvar = "Sep 11, 2010" ] ; then
    echo "yes"
fi

In the above example, if myvar equals "foo", the code will work as expected and will not print anything. However, if myvar equals "Sep 11, 2010", the code will fail with the following error:

[: too many arguments

In this case, the spaces in "$myvar" (which equals "Sep 11, 2010") end up confusing bash. After bash expands "$myvar" using its stupid macro substitution (completely unnecessary in this case)  it ends up with the following comparison:

[ Sep 11, 2010 = "Sep 11, 2010" ]

That's why typically shell string comparisons surround the string arguments with double-quotes.  Here's how the "Sep 11, 2010" comparison should have been written:

if [[ "$myvar" = "Sep 11, 2010" ] ; then
    echo "yes"
fi

Double quotes vs. single quotes quoting of variables

If you want your environment variables to be expanded, you must enclose them in double quotes, rather than single quotes. Single quotes disable variable (as well as history) expansion.

Shell instant arithmetic expressions

You can perform simple integer math using shell constructs. Simply enclose the particular arithmetic expression between a "$((" and a "))", and bash will evaluate the expression. Here are some examples:

$ echo $(( 100 / 3 ))
33
$ myvar="56"
$ echo $(( $myvar + 12 ))
68
$ echo $(( $myvar - $myvar ))
0
$ myvar=$(( $myvar + 1 ))
$ echo $myvar
57

Compound comparisons and their Gotchas

Compound conditionals are discussed at Compound conditionals

In a compound test, even quoting the string variable might not suffice.

[ -n "$string" -o "$a" = "$b" ]

may cause an error with some versions of Bash if  $string  is empty.  The only way to avoid this is to used stupid trick of appending an extra character inside double quotes, for example:

[[ "x$string" != x -o "x$a" = "x$b" ]]

In case of integer comparisons you can use leading zero

    [[ "o$value" -ne 0 -o "0$a" = "0$b" ]]

Here are logical operations supported by shell:

These are similar to the Bash comparison operators && and ||, used within double brackets.

[[ condition1 && condition2 ]]
The -o and -a operators work with the test command or occur within single test brackets.
if [[ "$exp1" -a "$exp2" ]

 


Top updates

Bulletin Latest Past week Past month
Google Search


NEWS CONTENTS

Old News ;-)

[Mar 13, 2011] Linux tip Bash test and comparison functions

The -o operator allows you to test various shell options that may be set using set -o option, returning True (0) if the option is set and False (1) otherwise, as shown in Listing 3.


Listing 3. Testing shell options
[ian@pinguino ~]$ set +o nounset
[ian@pinguino ~]$ [ -o nounset ];echo $?
1
[ian@pinguino ~]$ set -u
[ian@pinguino ~]$ test -o nounset; echo $?
0

Finally, the -a and -o options allow you to combine expressions with logical AND and OR, respectively, while the unary ! operator inverts the sense of the test. You may use parentheses to group expressions and override the default precedence. Remember that the shell will normally run an expression between parentheses in a subshell, so you will need to escape the parentheses using \( and \) or enclosing these operators in single or double quotes. Listing 4 illustrates the application of de Morgan's laws to an expression.


Listing 4. Combining and grouping tests
[ian@pinguino ~]$ test "a" != "$HOME" -a 3 -ge 4 ; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o 3 -lt 4 \) ]; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o '(' 3 -lt 4 ')' ")" ]; echo $?
1

(( and [[

The test command is very powerful, but somewhat unwieldy given its requirement for escaping and given the difference between string and arithmetic comparisons. Fortunately, bash has two other ways of testing that are somewhat more natural for people who are familiar with C, C++, or Java® syntax.

The (( )) compound command evaluates an arithmetic expression and sets the exit status to 1 if the expression evaluates to 0, or to 0 if the expression evaluates to a non-zero value. You do not need to escape operators between (( and )). Arithmetic is done on integers. Division by 0 causes an error, but overflow does not. You may perform the usual C language arithmetic, logical, and bitwise operations. The let command can also execute one or more arithmetic expressions. It is usually used to assign values to arithmetic variables.


Listing 5. Assigning and testing arithmetic expressions
[ian@pinguino ~]$ let x=2 y=2**3 z=y*3;echo $? $x $y $z
0 2 8 24
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 3 8 16
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 4 8 13

As with (( )), the [[ ]] compound command allows you to use more natural syntax for filename and string tests. You can combine tests that are allowed for the test command using parentheses and logical operators.


Listing 6. Using the [[ compound
[ian@pinguino ~]$ [[ ( -d "$HOME" ) && ( -w "$HOME" ) ]] &&
> echo "home is a writable directory"
home is a writable directory

The [[ compound can also do pattern matching on strings when the = or != operators are used. The match behaves as for wildcard globbing as illustrated in Listing 7.


Listing 7. Wildcard tests with [[
[ian@pinguino ~]$ [[ "abc def .d,x--" == a[abc]*\ ?d* ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def c" == a[abc]*\ ?d* ]]; echo $?
1
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* ]]; echo $?
1

You can even do arithmetic tests within [[ compounds, but be careful. Unless within a (( compound, the < and > operators will compare the operands as strings and test their order in the current collating sequence. Listing 8 illustrates this with some examples.


Listing 8. Including arithmetic tests with [[
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || (( 3 > 2 )) ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 -gt 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a -gt 2 ]]; echo $?
-bash: a: unbound variable


Conditionals

While you could accomplish a huge amount of programming with the above tests and the && and || control operators, bash includes the more familiar "if, then, else" and case constructs. After you learn about these, you will learn about looping constructs and your toolbox will really expand.

If, then, else statements

The bash if command is a compound command that tests the return value of a test or command ($?) and branches based on whether it is True (0) or False (not 0). Although the tests above returned only 0 or 1 values, commands may return other values. Learn more about these in the LPI exam 102 prep: Shells, scripting, programming, and compiling tutorial.

The if command in bash has a then clause containing a list of commands to be executed if the test or command returns 0, one or more optional elif clauses, each with an additional test and then clause with an associated list of commands, an optional final else clause and list of commands to be executed if neither the original test, nor any of the tests used in the elif clauses was true, and a terminal fi to mark the end of the construct.

Using what you have learned so far, you could now build a simple calculator to evaluate arithmetic expressions as shown in Listing 9.


Listing 9. Evaluating expressions with if, then, else
[ian@pinguino ~]$ function mycalc ()
> {
> local x
> if [ $# -lt 1 ]; then
> echo "This function evaluates arithmetic for you if you give it some"
> elif (( $* )); then
> let x="$*"
> echo "$* = $x"
> else
> echo "$* = 0 or is not an arithmetic expression"
> fi
> }
[ian@pinguino ~]$ mycalc 3 + 4
3 + 4 = 7
[ian@pinguino ~]$ mycalc 3 + 4**3
3 + 4**3 = 67
[ian@pinguino ~]$ mycalc 3 + (4**3 /2)
-bash: syntax error near unexpected token `('
[ian@pinguino ~]$ mycalc 3 + "(4**3 /2)"
3 + (4**3 /2) = 35
[ian@pinguino ~]$ mycalc xyz
xyz = 0 or is not an arithmetic expression
[ian@pinguino ~]$ mycalc xyz + 3 + "(4**3 /2)" + abc
xyz + 3 + (4**3 /2) + abc = 35

The calculator makes use of the local statement to declare x as a local variable that is available only within the scope of the mycalc function. The let function has several possible options, as does the declare function to which it is closely related. Check the man pages for bash, or use help let for more information.

As you saw in Listing 9, you need to make sure that your expressions are properly escaped if they use shell metacharacters such as (, ), *, >, and <. Nevertheless, you have quite a handy little calculator for evaluating arithmetic as the shell does it.

You may have noticed the else clause and the last two examples in Listing 9. As you can see, it is not an error to pass xyz to mycalc, but it evaluates to 0. This function is not smart enough to identify the character values in the final example of use and thus be able to warn the user. You could use a string pattern matching test such as
[[ ! ("$*" == *[a-zA-Z]* ]]
(or the appropriate form for your locale) to eliminate any expression containing alphabetic characters, but that would prevent using hexadecimal notation in your input, since you might use 0x0f to represent 15 using hexadecimal notation. In fact, the shell allows bases up to 64 (using base#value notation), so you could legitimately use any alphabetic character, plus _ and @ in your input. Octal and hexadecimal use the usual notation of a leading 0 for octal and leading 0x or 0X for hexadecimal. Listing 10 shows some examples.


Listing 10. Calculating with different bases
[ian@pinguino ~]$ mycalc 015
015 = 13
[ian@pinguino ~]$ mycalc 0xff
0xff = 255
[ian@pinguino ~]$ mycalc 29#37
29#37 = 94
[ian@pinguino ~]$ mycalc 64#1az
64#1az = 4771
[ian@pinguino ~]$ mycalc 64#1azA
64#1azA = 305380
[ian@pinguino ~]$ mycalc 64#1azA_@
64#1azA_@ = 1250840574
[ian@pinguino ~]$ mycalc 64#1az*64**3 + 64#A_@
64#1az*64**3 + 64#A_@ = 1250840574

Additional laundering of the input is beyond the scope of this tip, so use your calculator with care.

The elif statement is very convenient. It helps you in writing scripts by allowing you to simplify the indenting. You may be surprised to see the output of the type command for the mycalc function as shown in Listing 11.


Listing 11. Type mycalc
[ian@pinguino ~]$ type mycalc
mycalc is a function
mycalc ()
{
local x;
if [ $# -lt 1 ]; then
echo "This function evaluates arithmetic for you if you give it some";
else
if (( $* )); then
let x="$*";
echo "$* = $x";
else
echo "$* = 0 or is not an arithmetic expression";
fi;
fi
}

Of course, you could just do shell arithmetic by using $(( expression )) with the echo command as shown in Listing 12. You wouldn't have learned anything about functions or tests that way, but do note that the shell does not interpret metacharacters, such as *, in their normal role when inside (( expression )) or [[ expression ]].


Listing 12. Direct calculation in the shell with echo and $(( ))
[ian@pinguino ~]$ echo $((3 + (4**3 /2)))
35

If you'd like to know more about Bash scripting in Linux, read the tutorial "LPI exam 102 prep: Shells, scripting, programming, and compiling," from which this article was excerpted, or see the other Resources below. Don't forget to rate this page.

Advanced Bash Scripting Guide by M. Leo Cooper

freshmeat.net

The Advanced Bash Scripting Guide is both a reference and a tutorial on shell scripting. This comprehensive book (the equivalent of 880+ print pages) covers almost every aspect of shell scripting. It contains 340 profusely commented illustrative examples, a number of tables, and a cross-linked index/glossary. Not just a shell scripting tutorial, this book also provides an introduction to basic programming techniques, such as sorting and recursion. It is well suited for either individual study or classroom use. It covers Bash, up to and including version 3.2x.
http://www.tldp.org/LDP/abs/html/

Linux tip Bash test and comparison functions

20 Feb 2007

Are you confused by the plethora of testing and comparison options in the Bash shell? This tip helps you demystify the various types of file, arithmetic, and string tests so you will always know when to use test, [ ], [[ ]], (( )), or if-then-else constructs.
{

The Bash shell is available on many Linux® and UNIX® systems today, and is a common default shell on Linux. Bash includes powerful programming capabilities, including extensive functions for testing file types and attributes, as well as the arithmetic and string comparisons available in most programming languages. Understanding the various tests and knowing that the shell can also interpret some operators as shell metacharacters is an important step to becoming a power shell user. This article, excerpted from the developerWorks tutorial LPI exam 102 prep: Shells, scripting, programming, and compiling, shows you how to understand and use the test and comparison operations of the Bash shell.

This tip explains the shell test and comparison functions and shows you how to add programming capability to the shell. You may have already seen simple shell logic using the && and || operators, which allow you to execute a command based on whether the previous command exits normally or with an error. In this tip, you will see how to extend these basic techniques to more complex shell programming.

Tests

In any programming language, after you learn how to assign values to variables and pass parameters, you need to test those values and parameters. In shells, the tests set the return status, which is the same thing that other commands do. In fact, test is a builtin command!

test and [

The test builtin command returns 0 (True) or 1 (False), depending on the evaluation of an expression, expr. You can also use square brackets: test expr and [ expr ] are equivalent. You can examine the return value by displaying $?; you can use the return value with && and ||; or you can test it using the various conditional constructs that are covered later in this tip.


Listing 1. Some simple tests
[ian@pinguino ~]$ test 3 -gt 4 && echo True || echo false
false
[ian@pinguino ~]$ [ "abc" != "def" ];echo $?
0
[ian@pinguino ~]$ test -d "$HOME" ;echo $?
0

In the first example in Listing 1, the -gt operator performs an arithmetic comparison between two literal values. In the second example, the alternate [ ] form compares two strings for inequality. In the final example, the value of the HOME variable is tested to see if it is a directory using the -d unary operator.

You can compare arithmetic values using one of -eq, -ne, -lt, -le, -gt, or -ge, meaning equal, not equal, less than, less than or equal, greater than, and greater than or equal, respectively.

You can compare strings for equality, inequality, or whether the first string sorts before or after the second one using the operators =, !=, <, and >, respectively. The unary operator -z tests for a null string, while -n or no operator at all returns True if a string is not empty.

Note: the < and > operators are also used by the shell for redirection, so you must escape them using \< or \>. Listing 2 shows more examples of string tests. Check that they are as you expect.


Listing 2. Some string tests
[ian@pinguino ~]$ test "abc" = "def" ;echo $?
1
[ian@pinguino ~]$ [ "abc" != "def" ];echo $?
0
[ian@pinguino ~]$ [ "abc" \< "def" ];echo $?
0
[ian@pinguino ~]$ [ "abc" \> "def" ];echo $?
1
[ian@pinguino ~]$ [ "abc" \<"abc" ];echo $?
1
[ian@pinguino ~]$ [ "abc" \> "abc" ];echo $?
1

Some of the more common file tests are shown in Table 1. The result is True if the file tested is a file that exists and that has the specified characteristic.
Table 1. Some common file tests
Operator Characteristic
-d Directory
-e Exists (also -a)
-f Regular file
-h Symbolic link (also -L)
-p Named pipe
-r Readable by you
-s Not empty
-S Socket
-w Writable by you
-N Has been modified since last being read

In addition to the unary tests above, you can compare two files with the binary operators shown in Table 2.

Table 2. Testing pairs of files
Operator True if
-nt Test if file1 is newer than file 2. The modification date is used for this and the next comparison.
-ot Test if file1 is older than file 2.
-ef Test if file1 is a hard link to file2.

Several other tests allow you to check things such as the permissions of the file. See the man pages for bash for more details or use help test to see brief information on the test builtin. You can use the help command for other builtins too.

The -o operator allows you to test various shell options that may be set using set -o option, returning True (0) if the option is set and False (1) otherwise, as shown in Listing 3.


Listing 3. Testing shell options
[ian@pinguino ~]$ set +o nounset
[ian@pinguino ~]$ [ -o nounset ];echo $?
1
[ian@pinguino ~]$ set -u
[ian@pinguino ~]$ test  -o nounset; echo $?
0

Finally, the -a and -o options allow you to combine expressions with logical AND and OR, respectively, while the unary ! operator inverts the sense of the test. You may use parentheses to group expressions and override the default precedence. Remember that the shell will normally run an expression between parentheses in a subshell, so you will need to escape the parentheses using \( and \) or enclosing these operators in single or double quotes. Listing 4 illustrates the application of de Morgan's laws to an expression.


Listing 4. Combining and grouping tests
[ian@pinguino ~]$ test "a" != "$HOME" -a 3 -ge 4 ; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o 3 -lt 4 \) ]; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o '(' 3 -lt 4 ')' ")" ]; echo $?
1

(( and [[

The test command is very powerful, but somewhat unwieldy given its requirement for escaping and given the difference between string and arithmetic comparisons. Fortunately, bash has two other ways of testing that are somewhat more natural for people who are familiar with C, C++, or Java® syntax.

The (( )) compound command evaluates an arithmetic expression and sets the exit status to 1 if the expression evaluates to 0, or to 0 if the expression evaluates to a non-zero value. You do not need to escape operators between (( and )). Arithmetic is done on integers. Division by 0 causes an error, but overflow does not. You may perform the usual C language arithmetic, logical, and bitwise operations. The let command can also execute one or more arithmetic expressions. It is usually used to assign values to arithmetic variables.


Listing 5. Assigning and testing arithmetic expressions
[ian@pinguino ~]$ let x=2 y=2**3 z=y*3;echo $? $x $y $z
0 2 8 24
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 3 8 16
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 4 8 13

As with (( )), the [[ ]] compound command allows you to use more natural syntax for filename and string tests. You can combine tests that are allowed for the test command using parentheses and logical operators.


Listing 6. Using the [[ compound
[ian@pinguino ~]$ [[ ( -d "$HOME" ) && ( -w "$HOME" ) ]] && 
>  echo "home is a writable directory"
home is a writable directory

The [[ compound can also do pattern matching on strings when the = or != operators are used. The match behaves as for wildcard globbing as illustrated in Listing 7.


Listing 7. Wildcard tests with [[
[ian@pinguino ~]$ [[ "abc def .d,x--" == a[abc]*\ ?d* ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def c" == a[abc]*\ ?d* ]]; echo $?
1
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* ]]; echo $?
1

You can even do arithmetic tests within [[ compounds, but be careful. Unless within a (( compound, the < and > operators will compare the operands as strings and test their order in the current collating sequence. Listing 8 illustrates this with some examples.


Listing 8. Including arithmetic tests with [[
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || (( 3 > 2 )) ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 -gt 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a -gt 2 ]]; echo $?
-bash: a: unbound variable

Conditionals

While you could accomplish a huge amount of programming with the above tests and the && and || control operators, bash includes the more familiar "if, then, else" and case constructs. After you learn about these, you will learn about looping constructs and your toolbox will really expand.

If, then, else statements

The bash if command is a compound command that tests the return value of a test or command ($?) and branches based on whether it is True (0) or False (not 0). Although the tests above returned only 0 or 1 values, commands may return other values. Learn more about these in the LPI exam 102 prep: Shells, scripting, programming, and compiling tutorial.

The if command in bash has a then clause containing a list of commands to be executed if the test or command returns 0, one or more optional elif clauses, each with an additional test and then clause with an associated list of commands, an optional final else clause and list of commands to be executed if neither the original test, nor any of the tests used in the elif clauses was true, and a terminal fi to mark the end of the construct.

Using what you have learned so far, you could now build a simple calculator to evaluate arithmetic expressions as shown in Listing 9.


Listing 9. Evaluating expressions with if, then, else
[ian@pinguino ~]$ function mycalc ()
> {
>   local x
>   if [ $# -lt 1 ]; then
>     echo "This function evaluates arithmetic for you if you give it some"
>   elif (( $* )); then
>     let x="$*"
>     echo "$* = $x"
>   else
>     echo "$* = 0 or is not an arithmetic expression"
>   fi
> }
[ian@pinguino ~]$ mycalc 3 + 4
3 + 4 = 7
[ian@pinguino ~]$ mycalc 3 + 4**3
3 + 4**3 = 67
[ian@pinguino ~]$ mycalc 3 + (4**3 /2)
-bash: syntax error near unexpected token `('
[ian@pinguino ~]$ mycalc 3 + "(4**3 /2)"
3 + (4**3 /2) = 35
[ian@pinguino ~]$ mycalc xyz
xyz = 0 or is not an arithmetic expression
[ian@pinguino ~]$ mycalc xyz + 3 + "(4**3 /2)" + abc
xyz + 3 + (4**3 /2) + abc = 35
       
       
       
        

The calculator makes use of the local statement to declare x as a local variable that is available only within the scope of the mycalc function. The let function has several possible options, as does the declare function to which it is closely related. Check the man pages for bash, or use help let for more information.

As you saw in Listing 9, you need to make sure that your expressions are properly escaped if they use shell metacharacters such as (, ), *, >, and <. Nevertheless, you have quite a handy little calculator for evaluating arithmetic as the shell does it.

You may have noticed the else clause and the last two examples in Listing 9. As you can see, it is not an error to pass xyz to mycalc, but it evaluates to 0. This function is not smart enough to identify the character values in the final example of use and thus be able to warn the user. You could use a string pattern matching test such as
[[ ! ("$*" == *[a-zA-Z]* ]]
(or the appropriate form for your locale) to eliminate any expression containing alphabetic characters, but that would prevent using hexadecimal notation in your input, since you might use 0x0f to represent 15 using hexadecimal notation. In fact, the shell allows bases up to 64 (using base#value notation), so you could legitimately use any alphabetic character, plus _ and @ in your input. Octal and hexadecimal use the usual notation of a leading 0 for octal and leading 0x or 0X for hexadecimal. Listing 10 shows some examples.


Listing 10. Calculating with different bases
[ian@pinguino ~]$ mycalc 015
015 = 13
[ian@pinguino ~]$ mycalc 0xff
0xff = 255
[ian@pinguino ~]$ mycalc 29#37
29#37 = 94
[ian@pinguino ~]$ mycalc 64#1az
64#1az = 4771
[ian@pinguino ~]$ mycalc 64#1azA
64#1azA = 305380
[ian@pinguino ~]$ mycalc 64#1azA_@
64#1azA_@ = 1250840574
[ian@pinguino ~]$ mycalc 64#1az*64**3 + 64#A_@
64#1az*64**3 + 64#A_@ = 1250840574

Additional laundering of the input is beyond the scope of this tip, so use your calculator with care.

The elif statement is very convenient. It helps you in writing scripts by allowing you to simplify the indenting. You may be surprised to see the output of the type command for the mycalc function as shown in Listing 11.


Listing 11. Type mycalc
[ian@pinguino ~]$ type mycalc
mycalc is a function
mycalc ()
{
    local x;
    if [ $# -lt 1 ]; then
        echo "This function evaluates arithmetic for you if you give it some";
    else
        if (( $* )); then
            let x="$*";
            echo "$* = $x";
        else
            echo "$* = 0 or is not an arithmetic expression";
        fi;
    fi
}







 

Of course, you could just do shell arithmetic by using $(( expression )) with the echo command as shown in Listing 12. You wouldn't have learned anything about functions or tests that way, but do note that the shell does not interpret metacharacters, such as *, in their normal role when inside (( expression )) or [[ expression ]].


Listing 12. Direct calculation in the shell with echo and $(( ))
[ian@pinguino ~]$  echo $((3 + (4**3 /2)))
35







 

 List Constructs

The "and list" and "or list" constructs provide a means of processing a number of commands consecutively. These can effectively replace complex nested if/then or even case statements. Note that the exit status of an "and list" or an "or list" is the exit status of the last command executed.

and list
 
   1 command-1 && command-2 && command-3 && ... command-n
Each command executes in turn provided that the previous command has given a return value of true. At the first false return, the command chain terminates (the first command returning false is the last one to execute).


Example 3-87. Using an "and list" to test for command-line arguments

   1 #!/bin/bash
   2
   3 # "and list"
   4
   5 if [ ! -z $1 ] && echo "Argument #1 = $1" && [ ! -z $2 ] && echo "Argument #2 = $2"
   6 then
   7   echo "At least 2 arguments to script."
   8   # All the chained commands return true.
   9 else
  10   echo "Less than 2 arguments to script."
  11   # At least one of the chained commands returns false.
  12 fi 
  13 # Note that "if [ ! -z $1 ]" works, but its supposed equivalent,
  14 # "if [ -n $1 ]" does not. This is a bug, not a feature.
  15
  16
  17 # This accomplishes the same thing, coded using "pure" if/then statements.
  18 if [ ! -z $1 ]
  19 then
  20   echo "Argument #1 = $1"
  21 fi
  22 if [ ! -z $2 ]
  23 then
  24   echo "Argument #2 = $2"
  25   echo "At least 2 arguments to script."
  26 else
  27   echo "Less than 2 arguments to script."
  28 fi
  29 # It's longer and less elegant than using an "and list".
  30
  31
  32 exit 0

or list
 
   1 command-1 || command-2 || command-3 || ... command-n
Each command executes in turn for as long as the previous command returns false. At the first true return, the command chain terminates (the first command returning true is the last one to execute). This is obviously the inverse of the "and list".


Example 3-88. Using "or lists" in combination with an "and list"

   1 #!/bin/bash
   2
   3 # "Delete", not-so-cunning file deletion utility.
   4 # Usage: delete filename
   5
   6 if [ -z $1 ]
   7 then
   8   file=nothing
   9 else
  10   file=$1
  11 fi 
  12 # Fetch file name (or "nothing") for deletion message.
  13
  14
  15 [ ! -f $1 ] && echo "$1 not found. Can't delete a nonexistent file."
  16 # AND LIST, to give error message if file not present.
  17
  18 [ ! -f $1 ] || ( rm -f $1; echo "$file deleted." )
  19 # OR LIST, to delete file if present.
  20 # ( command1 ; command2 ) is, in effect, an AND LIST variant.
  21
  22 # Note logic inversion above.
  23 # AND LIST executes on true, OR LIST on false.
  24
  25 [ ! -z $1 ] ||  echo "Usage: `basename $0` filename"
  26 # OR LIST, to give error message if no command line arg (file name).
  27
  28 exit 0

Clever combinations of "and" and "or" lists are possible, but the logic may easily become convoluted and require extensive debugging.

Recommended Links

Softpanorama Top Visited

Softpanorama Recommended

Please visit  Heiner Steven SHELLdorado  the best shell scripting site on the Internet

Shelldorado - the best shell scripting site on the Web.

Advanced Bash-Scripting Guide

Linux tip Bash test and comparison functions

Unix - Linux Bourne - Bash Shell Scripting Tutorial [ steve-parker.org ]




Etc

Society

Groupthink : Understanding Micromanagers and Control Freaks : Toxic Managers : BureaucraciesHarvard Mafia : Diplomatic Communication : Surviving a Bad Performance Review : Insufficient Retirement Funds as Immanent Problem of Neoliberal Regime : PseudoScience : Who Rules America : Two Party System as Polyarchy : Neoliberalism  : The Iron Law of Oligarchy : Libertarian Philosophy

Quotes

Skeptical Finance : John Kenneth Galbraith : Keynes : George Carlin : Skeptics : Propaganda  : SE quotes : Language Design and Programming Quotes : Random IT-related quotes : Oscar Wilde : Talleyrand : Somerset Maugham : War and Peace : Marcus Aurelius : Eric Hoffer : Kurt Vonnegut : Otto Von Bismarck : Winston Churchill : Napoleon Bonaparte : Ambrose Bierce : Oscar Wilde : Bernard Shaw : Mark Twain Quotes

Bulletin:

Vol 26, No.1 (January, 2013) Object-Oriented Cult : Vol 25, No.12 (December, 2013) Rational Fools vs. Efficient Crooks: The efficient markets hypothesis : Vol 25, No.08 (August, 2013) Cloud providers as intelligence collection hubs : Vol 23, No.10 (October, 2011) An observation about corporate security departments : Vol 23, No.11 (November, 2011) Softpanorama classification of sysadmin horror stories : Vol 25, No.05 (May, 2013) Corporate bullshit as a communication method : Vol 25, No.10 (October, 2013) Cryptolocker Trojan (Win32/Crilock.A) : Vol 25, No.06 (June, 2013) A Note on the Relationship of Brooks Law and Conway Law

History:

Fifty glorious years (1950-2000): the triumph of the US computer engineering : Donald Knuth : TAoCP and its Influence of Computer Science : Richard Stallman : Linus Torvalds  : Larry Wall  : John K. Ousterhout : CTSS : Multix OS Unix History : Unix shell history : VI editor : History of pipes concept : Solaris : MS DOSProgramming Languages History : PL/1 : Simula 67 : C : History of GCC developmentScripting Languages : Perl history   : OS History : Mail : DNS : SSH : CPU Instruction Sets : SPARC systems 1987-2006 : Norton Commander : Norton Utilities : Norton Ghost : Frontpage history : Malware Defense History : GNU Screen : OSS early history

Classic books:

The Peter Principle : Parkinson Law : 1984 : The Mythical Man-MonthHow to Solve It by George Polya : The Art of Computer Programming : The Elements of Programming Style : The Unix Hater’s Handbook : The Jargon file : The True Believer : Programming Pearls : The Good Soldier Svejk : The Power Elite

Most popular humor pages:

Manifest of the Softpanorama IT Slacker Society : Ten Commandments of the IT Slackers Society : Computer Humor Collection : BSD Logo Story : The Cuckoo's Egg : IT Slang : C++ Humor : ARE YOU A BBS ADDICT? : The Perl Purity Test : Object oriented programmers of all nations : Financial Humor : Financial Humor Bulletin, 2008 : Financial Humor Bulletin, 2010 : The Most Comprehensive Collection of Editor-related Humor : Programming Language Humor : Goldman Sachs related humor : Greenspan humor : C Humor : Scripting Humor : Real Programmers Humor : Web Humor : GPL-related Humor : OFM Humor : Politically Incorrect Humor : IDS Humor : "Linux Sucks" Humor : Russian Musical Humor : Best Russian Programmer Humor : Microsoft plans to buy Catholic Church : Richard Stallman Related Humor : Admin Humor : Perl-related Humor : Linus Torvalds Related humor : PseudoScience Related Humor : Networking Humor : Shell Humor : Financial Humor Bulletin, 2011 : Financial Humor Bulletin, 2012 : Financial Humor Bulletin, 2013 : Java Humor : Software Engineering Humor : Sun Solaris Related Humor : Education Humor : IBM Humor : Assembler-related Humor : VIM Humor : Computer Viruses Humor : Bright tomorrow is rescheduled to a day after tomorrow : Classic Computer Humor

 

The Last but not Least


Copyright © 1996-2014 by Dr. Nikolai Bezroukov. www.softpanorama.org was created as a service to the UN Sustainable Development Networking Programme (SDNP) in the author free time. This document is an industrial compilation designed and created exclusively for educational use and is distributed under the Softpanorama Content License. Site uses AdSense so you need to be aware of Google privacy policy. Original materials copyright belong to respective owners. Quotes are made for educational purposes only in compliance with the fair use doctrine. This is a Spartan WHYFF (We Help You For Free) site written by people for whom English is not a native language. Grammar and spelling errors should be expected. The site contain some broken links as it develops like a living tree...

You can use PayPal to make a contribution, supporting hosting of this site with different providers to distribute and speed up access. Currently there are two functional mirrors: softpanorama.info (the fastest) and softpanorama.net.

Disclaimer:

The statements, views and opinions presented on this web page are those of the author and are not endorsed by, nor do they necessarily reflect, the opinions of the author present and former employers, SDNP or any other organization the author may be associated with. We do not warrant the correctness of the information provided or its fitness for any purpose.

Last modified: February 19, 2014