Softpanorama

May the source be with you, but remember the KISS principle ;-)
Contents Bulletin Scripting in shell and Perl Network troubleshooting History Humor

if statements in Bash

News

Bash

Recommended Links

Reference Short-circuit if statements Examples Additional examples
Checking the exit status of a command Test: Obsolete construct for testing Files and Strings Short circuit operators && and || Double square bracket conditions String comparisons Numeric Conditionals File Attribute Checking
Sequences of commands Arithmetic expressions Shell Conditional Operators Caveats BASH Debugging Shell history Humor Etc

Introduction

Note: Those are my old lecture notes for FDU students. They use copyrighted examples from several sources such as Advanced Bash-Scripting Guide and as such can be used for educational purposes only in accordance with fair use doctrine.

Each command  is bash produces so called return code which indicates whether the command was executed successfully. It is stored in a special system variable named $?. In this case the command  executed successfully returns zero. It there some problems the result in not zero and higher is the number the more severe error was encountered during the  execution of command. This is a very useful information in scripts, but we need somehow to check this variable. The if command provides this opportunity.

The built-in if command runs a command and checks the resulting return code. If return code is zero (the command was successful), it executes then branch. If it is non-zero it executed else branch if it exists.

 Any Unix utility or command can be used with the if command, not only test.

The syntax of Bash if command is as follows

if test arguments ; then
   # statements to run if test succeed (the command  returned RC=0)
else
   # statements to run if the  text failed (return RC >0)
fi
Essentially that means *Using modern bash double round quote expressions):
test arguments
if (( $? = 0 )) ; then 
   # statements to run if test succeed (the command  returned RC=0)
else
   # statements to run if the  text failed (return RC >0)
fi
Here test is an external program.  Any other program can be used instead of test. After the program was executed the return code of the program is evaluated to decide whether execute then or else branch of if statement. 

Like in all programming languages else part is optional. As you understand this "original" notation looks pretty ugly. So it is not surprising that some cosmetics was introduced and instead of test you can use square brackets. This is just syntax sugar, test command is invoked anyway, but it makes scripts less ugly. You can think about "[" as an alias to test  (actually in Cygwin this how it is implemented)  and "]" as a special form of the comment statement :-)

if [ -f ./machines ] ; then
  echo "The machines file exists!"
else 
  echo "The machines file does not exits
  exit
fi
Again this legacy construct should be never used in your scripts as bash provide with two new, better constructs -- so called double quotes expression -- [[ ...]] and double round brackets conditional expression.  So the example above can (and should)  be rewritten as
if [[ -f ./machines ]] ; then
  echo "The machines file exists!"
else 
  echo "The machines file does not exits
  exit
fi 

Please note that within double square brackets bash will not perform word splitting or pathname expansion, making in most case double quotes unnecessary, if the expression uses variables values of which have a non zero length.  The message "The report file ./report.out exists!" is printed only if the file exists. If the file does not exist, the printf command is skipped and execution proceeds after the fi.

NOTES:

More about the most primitive form of if  statement: checking the exit status of a command

This form f if statement, which we called "most primitive form of if statement still have some used. For example:

if cd /fake; then echo "cd returned OK"; fi

If you add an else clause, you get the ability to execute one set of statements if a condition is true or another set of statements if the condition is false. If the status is 0, the condition evaluates to true; if it is anything else, the condition is considered false. The same is true for each condition attached to an elif statement (if any).

For example:
if cd /Media/Packages ; then 
   echo "cd returned OK";  
else 
   echo "cd failed"; 
fi

Conditional expressions: a step toward modern if statement syntax

Conditional expressions were not part of the original Borne shell (which was a blunder on the part of its designer even in the situation when memory was tight -- ancient computers that run Unix often have one megabyte (megabyte, not gigabyte) of memory or even less. In original Borne shell conditional expressions were implemented via external command called test which accepts sequence of arguments which are interpreted by this program as expression.  As later version of shell (Korn shell) introduced double square brackets and double round brackets expressions which are much better deal, this archaic way  should better be forgotten (double square brackets or double round brackets expressions should be used exclusively in you new scrips), but it is preserved for compatibility so we  need to know  about its existence.

The test command evaluates expression and return the corresponding "return  code" -- 0 if expression is true, and non-zero it is false. 

Now let's see hot it issued within bash if command, which allow to structure control stream of your scripts into two branches: then branch which is executed if the condition is true and (optional) else branch which is executed, if the conditions false.

The simplest type of flow control construct is the conditional, embodied in the shell if statement, which using Algol-68 syntax and actually is the only survivor of Algol-68 syntax among currently used languages.

You use a conditional when you want to choose whether or not to do something, or to choose among a small number of things to do, according to the truth or falsehood of conditions. Conditions test values of shell variables, characteristics of files, whether or not commands run successfully, and other factors. The shell has a large set of built-in tests that are relevant to the task of shell programming.

There are two major types conditional expressions in bash that we should used exclusively -- doub aqare bracket expression and double round braket expression (see Arithmetic expressions and Comparison operators for more information).  All other, legacy types can be safely ignored.

For example: 

    [[ $user = 'nick' ]]  # string comparison condition 
    [[ $user != 'root' ]] # string comparison condition
    (( uid == 0 ))
    (( uid > 1000 ))
    (( ( $uid > 10 ) && ( $uid < 100 ) )) # arithmetic condition

Regular expression can also be used in if statement. An expression in bash can be understood as formula that calculates some value. There are several types of expressions in bash. We will mention only three:

  1. Arithmetic Expressions
  2. String Expressions
  3. File expressions

Bash simultaneously is a scripting language and a macro processor. So before expression is calculated its content is subjected to several passes of analysis on each of which specific language  element are processed (which is called macro expressions). In other languages there is a separate "preprocessor" phase, performed by a separate program. But in bash they are integrated into interpreter. During this process of macroexpansion macrovariables are replaced by their values. Each such action is usually called  macrosubstitution or simply substitution.  In case of double quoted literals the process of substituting of values of Bash variables into their  values is also called interpolation (the term more widely used in Perl then in bash).  

Bash performs certain types of macrosubstitution only outside literals(single or double quoted strings and backticked string are called literals) . For example Bash expands  tilde  to the user home directory only outside any literals

Outside literals after the values of variables and backticked strings were substituted Bash performs two additional operations:

  1. Word splitting of the resulting string ( arguments separation by whitespace, or the contents of the IFS variable). Outside double quoted literals there is  a real danger, that the string resulting from substitution of the  value of a variable contains spaces.  Such string  will be treated as several "tokens" by syntax analyzer, which often leads to syntax error. Or is the variable expanded to zero length string.  In this case the  systex analyser will report missing token. That's why you see "paranoid" style of in olde shells (and sometimes even in bash) where when you compare two variables in if statement, noth variable are enclosed in double quotes. This is done to prevent errors mentioned above. But in modern bash in most case this is unneeded precousion,  if you use double square or round bracket expressions, so this paranoid style is to a certain extent an archaism: just use [[...]] or ((...)) expression and you will be fine.
  2. Pathname expansion ( aka pathname pattern matching using basic regex)

The result is passed to syntax phase of Bash interpreter, where the syntax analyzer try to split script text into statements. If errors are found the process is aborted. After that script is executed one statement at a time.

The order is important and can cause subtle problems in scripts. Suppose you assign a path with a tilde in it in a variable (tile means home directory in Bash):

ls -d ~/bin
/home/joeuser/bin

You can see the tilde here work as expected. Now let's enclose the expression ~/bin in double quotes:

$ TEMP="~/bin"
$ ls $TEMP
~/bin not found

You can see that this does not work -- tilde substitution double  quoted literals not performed as explained above. In double quotes literals tilde symbol is not treated as marcosymbol, representing home directory, but as a regular symbol. that means that within any literal including double quote literals the tilde will remain  "as is". In such cases you should use the $HOME variable instead.

Modern if construct in Bash

The modern form of  if construct in bash has the following syntax:

if condition
then
    statements
[elif condition
    then statements...]
[else
    statements]
fi
The elif or else parts can be omitted.

You can use as many elif (a contraction of "else if") clauses as you wish; they introduce more conditions, and thus more choices for which set of statements to execute. If you use one or more elifs, you can think of the else clause as the "if all else fails" part.

There are five forms of an IF statement in shell

  1. If statement which checks the exit status of a command
  2. test: Testing Files and Strings (If statements that evaluates conditional expression using test)
  3. Short circuit if statements ( Conditional Execution && and || ). See also Short-circuit if statements
  4. Double square bracket condition test
  5. If statement with double round parenthesis conditionals (Numeric Conditionals)
  6. Conditionals with File Attribute Checking

The idea of implicit type conversion

Shell is the unique  language in one more respect: it casts operators into strings or numbers depending of the type of operation used (implicit type conversion).  In other owrd operator dictates the type into which the variable is converted before performing a particular opration.  This is completely different approach from the most of programming language in which comparison operators are "polymorphic" -- work for all types of operators such  as integers, floating point numbers and string. Not so in bash and other shells.

That's why shell uses two set of operators: one for integer comparisons and the other for string comparisons.

That greatly confuse novice programmers, but this idea is not so crazy as you can thing from the first sight. It has its value and is probably one of the possible way to design the language. The  only other language that adopted this non-orthodox idea of implicit type conversion  based on operators used is Perl. And that create the barrier of entry for Perl too.. This one reason why Python, which is a more traditional language overtook Perl.

This is what is called "Basic language effect" in action -- simpler language what can be  quickly learned by novices have much  better chances to survive and prosper despite all problems with it design, than a more complex  language with less defects in the design, but for example, more convoluted syntax  of semantic rules.  We see the same effect also with PHP.  

So the second way to classify and study conditional expressions is by  type they convert the operands. 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 ( there is a new style ((...)) notation and two old style notations [[...]] or [...]  which allow integer comparisons )
  2. String comparison operators ( Double square bracket conditional expression )
  3. File test operators  they can be ither unarly like -f  /etc/resolv.conf  or binary. They be used in both double square bracket (preferable), of single square bracket conditional expression ).

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 re-implementing all the quirks of Borne shell, plus some.The following three rules (you can call them Softpanorama 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. The later generally should be classified as programming mistakes.

Tips:

test: Obsolete construct for Testing Files and Strings

Historically conditional expressions in Unix shells were introduced via test command. For instance, test can check whether a file is writable before your script tries to write to it. It can treat the string in a shell variable as a number and do comparisons ("Is that number less than 1000?"). You can combine tests, too ("If the file exists and it's readable and the message number is more than 500..."). Some versions of test have more tests than others.

The test command returns a zero status if the test was true and a nonzero status otherwise, so people usually use test with if , while, or until. Here's a way your program could check to see if the user has a readable file named .profile in the home directory:

if test -r $HOME/.profile
then
    echo "$myname: You already have .profile file and its readable"
else
    echo " you do not have .profile file. Copying ..."
    cp /etc/skel/.profile $HOME/.profile
    exit 1
fi

The test command also lets you test for something that isn't true. Add an exclamation point (!) before the condition you're testing. For example, the following test is true if the .profile file is not readable:

if test ! -r $HOME/.profile 
... ... ...

The hack that was implemented is to link text to the file named [. Yes, that's a left bracket. It was a pretty interesting hack: you can use it interchangeably with the test command with one exception: there has to be a matching right bracket (]) at the end of the test. The second example above could be rewritten this way:

if [ ! -r $HOME/.profile ]
then
    echo "$myname: Can't read your '.profile'.  You need to create one and make it readable." 1>&2
    exit 1
fi

Be sure to leave space between the brackets and other text. There are a couple of other common gotchas caused by empty arguments because shell attempts macro expansion before syntax analysis.

Again this construct is obsolete and generally should be avoided.

Short circuit operators && and ||

BASH allows you to combine exit statuses logically, so that you can test more than one thing at a time:

In both cases it's useful to think about them as "short-circuit and" and "short-circuit or," respectively:

In if statement consists of two conditions (which in turn can be statements ;-) connected by && like in following example

if condition1 && condition2
then
    ...
fi

condition1 is always executed and the result is used to decide if condition 2 should be evaluated. If and only if the first statement returns a return code 0 conditions2 is evaluated (executed). The then clause will be executed only if both succeeded.

In case of short-circuit or the situation is similar:

if statement1 || statement2
then
    ...
fi

statement1 is always executed. If it returns code zero (success), then statement2 will not be executed. Otherwise statement2 will be executed and its return code will be used for the deciding whether to execute then clause of the if statement or else clause. In other words then clause runs either statement1 or statement2 returns a zero code.

true && echo "Yes."
Yes. 
true || echo "No."
No
false || echo "Yes."
Yes.

Note: These constructs can be used outside if statement as well. They provide an elegant way to implement if statement with only else clause.

Let's assume that we need to write a script that checks a /etc/passwd file for the presence of two users who left the company. We can use grep for this: it returns exit status 0 if it found the given string in its input, non-0 if not:

user=$1
if grep $user /etc/passwd || -e $home/$user
then
    print "user $user is not fully removed from the server."
fi

Numeric Conditionals

Most modern shells implements special construct for numeric conditions called ((...)) construct. See Arithmetic Expressions in BASH. For example

function mess
{
   if (( "$1" > 0 )) ; then
      total=$1
   else
      total=100
   fi
   tail -$total /var/adm/messages | more
}

The shell also provides a older set of arithmetic tests. These are different from character string comparisons like < and >, which compare lexicographic values of strings, not numeric values. For example, "6" is greater than "57" lexicographically, just as "p" is greater than "ox,"

Note: numeric comparisons in double square brackets are obsolete. You should use (( ... )) construct.

Arithmetic Test Operators

Test Comparison
-lt Less than
-le Less than or equal
-eq Equal
-ge Greater than or equal
-gt Greater than
-ne Not equal

You'll find these to be of the most use in the context of the integer variables we'll see in the next chapter. They're necessary if you want to combine integer tests with other types of tests within the same conditional expression.

However, the shell has a separate syntax for conditional expressions that involve integers only. It's considerably more efficient, so you should use it in preference to the arithmetic test operators listed above. Again, we'll cover the shell's integer conditionals in the next chapter.

In sysadmin scripts often the if statement is used for checking the result of a pipe

no_files=`ls -1 | wc -l`
if  (( $no_files < $MIN_TESTS )) ; then
   printf "%s\n" "Too few results...Please check your test suit"
   exit 192
fi

Please note that the semicolon before the then is required. then is technically a separate command, although it works in conjunction with if . Because they are on one line, the semicolon is needed to separate the commands.

if commands can be nested inside other if commands.

no_files=`ls -1 | wc -l`
if (( $no_files < $TOOFEW )) ; then
   printf "%s\n" "Too few files...but will process them anyway"
else
   if (( $no_files -gt $TOOMANY )) ; then
      printf "%s\n" "There are many files."
   else
      printf "%s\n" "Starting to process the files"
   fi
fi

The commands cannot be cross-nested; the inner if must always be completed before the outer if .

To choose between a series of alternatives, if commands can have an elif part. elif is a shortcut for else if and reduces unnecessary if nesting. The elif part can be followed by a final else part that, if it is present, is executed only when there are no alternatives. Combining these ideas, you can rewrite the previous example as follows.

no_files=`ls -1 | wc -l`
if  (( "$no_files" < "$TOOFEW" )) ; then
   printf "%s\n" "Too few files...but will process them anyway"
elif (( "$no_files" -gt "$TOOMANY" )) ; then
   printf "%s\n" "There are many files.  Processing may take a long time"
else
   printf "%s\n" "Starting to process the files"
fi

The if command doesn't have to be used with the test command. It can run and test the status code of any command.

if rm "$TEMPFILE" ; then
   printf "%s\n" "$SCRIPT:temp file deleted"
else
   printf "%s - status code %d\n" "$SCRIPT:$LINENO: unable to delete temp file" $? 2>&
fi

Embedding complex commands into an if command can make a script difficult to read and debug; you should avoid doing this. In this case, the rm command is not as prominent as it would be if it appeared on a line of its own. Likewise, it is possible to declare variables inside of an if command, but it makes it very difficult to determine which variables exist and which do not.

 

Double square bracket conditions

The [[ ]] construct was introduced in ksh88 as a way to compensate for multiple shortcomings and limitations of the [ ] (test) solution. Essentially it makes [ ] construct obsolete.

The [[ ]] construct expects expression. What is important delimiters [[ and ]] serve as double quotes so you do not have macro expansion inside: tilde  substitution and wildcard expansion aren't done within [[ and ]], making quoting less necessary.

One of the [[ ]] construct warts is that it redefined == as a pattern matching operation, which anybody who programmed in C/C++/Java strongly resent. Latest bash version corrected that and allow using Perl-style =~ operator instead (I think ksh93 allow that too), but preserved old "extension" as well :

string="abba"
[[ $string =∼ [aeiou] ]]
echo $?
0

[[ $string =∼ h[sdfghjkl] ]]
echo $?
1

As you see from the examples above [[ ]] construct can be used as a separate statement that returns an exit status depending upon whether condition is true or not. With && and || constructs discussed above this provides an alternative syntax for if-then and if-else constructs

if [[ -d $HOME/$user ]] ; then echo " Home for user $user exists..."; fi

can be written simpler as

[[ -d $HOME/$user ]] && echo " Home for user $user exists..."  # shortcut version of if-then construct

There are three types of expressions that can be used inside [[ ... ]] construct:

Notes:

Operator True if...
str = pat[5]
str == pat[5]
str matches pat.

Note:  that's not what you logically expect, if you have some experience with C /C++/Java programming !!!

str != pat str does not match pat.
str1 < str2 str1 is less than str2 is collation order used
str1 > str2 str1 is greater than str2.
-n str str is not null (has length greater than 0).
-z str str is null (has length 0).
file1 -ef file2 file1 is another name for file2 (hard or symbolic link)

 

Several types of comparison are supported:

DAY=`date '+%a'`
if [ "$DAY" = "Mon" ] ; then
   printf "The weekend is over...get to work!\n"
fi 

The -z (zero length) and -n (not zero length) switches are short forms of = "" and != "", respectively.

If you are used to other computer languages, remember that the quotation marks used in the test command are not only used for string delineation but also for special character handling.

A common use for string comparisons is the testing of shell flag variables. Flags are variables used to indicate whether a particular condition is true. They provide a way to remember previous tests.

Any pair of values can be used to represent the flag's condition, such as true and false or yes and no. However, this can lead to ambiguous conditions when the flag contains an unexpected string such as "NO". Traditionally, if the variable contains anything except a null string, the condition is considered true.

declare -i WEEKEND=0;
DAY=`date '+%a'`
if [[ "$DAY" = "Sat" || "$DAY" = "Sun" ]] ; then
   WEEKEND=1
fi
if (( $WEEKEND==1 )); then
   cores=48
else
   cores=24
fi

 

Multiple Tests

Single tests can be combined together with  && (and) and || (or) switches. The test for existence of the generated file  in the example above can be rewritten as:

if [[ -f ./machines && -s ./machines ]] ; then
   echo "$SCRIPT:$LINENO: Machines files was generated correctly"
else
   echo "$SCRIPT:$LINENO: Machine file iether does not exists or have zero length. Aborting  execution... " 
   exit 192
fi

 

File Attributes Checking (file expressions)

The other kind of operator that can be used in conditional expressions checks a file for certain properties. There are approximately two dozens of such operators. Most common are listed below; the rest refer to arcana like sticky bits, sockets, and file descriptors, and thus are of interest only to systems programmers and/or hackers.

Note: ksh88 and Posix shell does not support -e (file exist). For compatibility you can use -f if this is a file

Operator True if...
-a file file exists
-d file file is a directory
-f file file is a regular file (i.e., not a directory or other special type of file)
-r file You have read permission on file
-s file file exists and is not empty
-w file You have write permission on file
-x file You have execute permission on file, or directory search permission if it is a directory
-O file You own file
-G file Your group ID is the same as that of file
file1 -nt file2 file1 is newer than file2
file1 -ot file2 file1 is older than file2

Before we get to an example, you should know that conditional expressions inside [[ and ]] can also be combined using the logical operators && and ||, just as we saw with plain shell commands above, in the section entitled "Combinations of Exit Statuses."

It's also possible to combine shell commands with conditional expressions using logical operators, like this:

if command && [[ condition ]]; then ...

Note: You can also negate the truth value of a conditional expression by preceding it with an exclamation point (!), so that ! expr evaluates to true only if expr is false.

if [[ ! -d "/root" ]]; then
   print -n "The directory /root does not exist on the server "
fi

Furthermore, you can make complex logical expressions of conditional operators by grouping them with parentheses. construct (statement list) runs the statement list in a subshell, whose exit status is that of the last statement in the list, It can also be used outside if statement.

Now let's write a script that prints essentially the same information as ls -l but in a more user-friendly way. It provides an excellent illustration of many file operations used:

function fileinfo
{
if [[ ! -a $1 ]]; then
    print "file $1 does not exist."
    return 1 # nothing to do
fi
if [[ -d $1 ]]; then
    print -n "$1 is a directory that you may "
    if [[ ! -x $1 ]]; then
        print -n "not "
    fi
    print "search."
elif [[ -f $1 ]]; then
    print "$1 is a regular file."
else
    print "$1 is a special file."
fi
if [[ -O $1 ]]; then
    print 'you own the file.'
else
    print 'you do not own the file.'
fi
if [[ -r $1 ]]; then
    print 'you have read permission on the file.'
fi
if [[ -w $1 ]]; then
    print 'you have write permission on the file.'
fi
if [[ -x $1 && ! -d $1 ]]; then
    print 'you have execute permission on the file.'
fi
return 0
}

Here's how this function works:

Chaining different types of conditional expressions

You can use different type of conditional expression  is a single if statement "chained" with && and ||.  that allow using ((...)) and [[...]] conditional expression together. For example

if (( $NDS_flag > 0 )) && [[  ! -f /etc/resolv.conf ]] ; then
    cat /etc/resolv.conf <EOF
search example.com local.lan
nameserver 127.0.0.1
nameserver 172.16.1.254
EOF
fi

Examples

The if/then construct tests whether a condition is true, and if so, executes one or more commands. Note that in this context, 0 (zero) will evaluate as true, as will a random string of alphanumeric. Puzzling out the logic of this is left as an exercise for the reader.

Example 1: An interesting usage of ability to execute statements in if construct:

if grep "can't find" <(nslookup sandbox.firma.com) ; then
   echo "DNS enty for sandbox.firma.com does not exists"
fi

Example 2: (adapted from Example 3-9Advanced Bash-Scripting Guide)

What is truth?

   #!/bin/bash
   if [[ 0 ]] #zero
      then
        echo "0 is true."
      else
        echo "0 is false."
   fi
 
   if [[ ]]  #NULL (empty condition)
   then
        echo "NULL is true."
   else
      echo "NULL is false."
   fi

   if [[ "xyz" ]] #string
   then
         echo "Random string is true."
   else
     echo "Random string is false."
   fi

   if [[ $xyz ]]  # uninitialized variable
   then
      echo "Uninitialized variable is true."
   else
      echo "Uninitialized variable is false."
   fi
 
   exit 0

Example 3: Use of ;? to prevent  "non-defined variablee" situation

filename=${1:?"filename missing."}
howmany=${2:-10}
sort -nr $filename | head -$howmany

Or

if [[ -z $1 ]]; then
    print 'usage: howmany filename [-N]'
else
    filename=$1
    howmany=${2:-10}
    sort -nr $filename | head -$howmany
fi

else statement can be avoided if you use exist after detecting the error. Therefore, a more usual style for shell programming is this:

if [[ -z $1 ]]; then
    print 'usage: howmany filename [-N]'
    return 1
fi
filename=$1
howmany=${2:-10}
sort -nr $filename | head -$howmany

 

Additional examples from Advanced Bash-Scripting Guide

From Advanced Bash-Scripting Guide

Example 3-10. Equivalence of [ ] and test

   1 #!/bin/bash
   2
   3 echo
   4
   5
   6 if test -z $1
   7 then
   8   echo "No command-line arguments."
   9 else
  10   echo "First command-line argument is $1."
  11 fi
  12
  13 # Both code blocks are functionally identical.
  14
  15 if [ -z $1 ]
  16 # if [ -z $1
  17 # also works, but outputs an error message.
  18 then
  19   echo "No command-line arguments."
  20 else
  21   echo "First command-line argument is $1."
  22 fi
  23
  24
  25 echo
  26
  27 exit 0

Example 3-11. Tests, command chaining, redirection

   1 #!/bin/bash
   2
   3 # This line is a comment.
   4
   5 filename=sys.log
   6
   7 if [ ! -f $filename ]
   8 then
   9   touch $filename; echo "Creating file."
  10 else
  11   cat /dev/null > $filename; echo "Cleaning out file."
  12 fi
  13
  14 # Of course, /var/log/messages must have
  15 # world read permission (644) for this to work.
  16 tail /var/log/messages > $filename
  17 echo "$filename contains tail end of system log."
  18
  19 exit 0

Example 3-12. arithmetic and string comparisons

   1 #!/bin/bash
   2
   3 a=4
   4 b=5
   5
   6 # Here a and b can be treated either as integers or strings.
   7 # There is some blurring between the arithmetic and integer comparisons.
   8 # Be careful.
   9
  10 if [ $a -ne $b ]
  11 then
  12   echo "$a is not equal to $b"
  13   echo "(arithmetic comparison)"
  14 fi
  15
  16 echo
  17
  18 if [ $a != $b ]
  19 then
  20   echo "$a is not equal to $b."
  21   echo "(string comparison)"
  22 fi
  23
  24 echo
  25
  26 exit 0

Example 3-13. testing whether a string is null

   1 #!/bin/bash
   2
   3 # If a string has not been initialized, it has no defined value.
   4 # This state is called "null" (not the same as zero).
   5
   6
   7 if [ -n $string1 ]   # $string1 has not been declared or initialized.
   8 then
   9   echo "String \"string1\" is not null."
  10 else
  11   echo "String \"string1\" is null."
  12 fi
  13 # Wrong result.
  14 # Shows $string1 as not null, although it was not initialized.
  15
  16 echo
  17
  18 # Lets try it again.
  19
  20 if [ -n "$string1" ]  # This time, $string1 is quoted.
  21 then
  22   echo "String \"string1\" is not null."
  23 else
  24   echo "String \"string1\" is null."
  25 fi
  26
  27 echo
  28
  29 if [ $string1 ]  # This time, $string1 stands naked.
  30 then
  31   echo "String \"string1\" is not null."
  32 else
  33   echo "String \"string1\" is null."
  34 fi
  35 # This works fine.
  36 # The [ ] test operator alone detects whether the string is null.
  37
  38 echo
  39
  40 string1=initialized
  41
  42 if [ $string1 ]  # This time, $string1 stands naked.
  43 then
  44   echo "String \"string1\" is not null."
  45 else
  46   echo "String \"string1\" is null."
  47 fi
  48 # Again, gives correct result.
  49
  50
  51 exit 0
  52
  53 # Thanks to Florian Wisser for pointing this out.

Example 3-14. zmost

   1 #!/bin/bash
   2
   3 #View gzipped files with 'most'
   4
   5 NOARGS=1
   6
   7 if [ $# = 0 ]
   8 # same effect as:  if [ -z $1 ]
   9 then
  10   echo "Usage: `basename $0` filename" >&2
  11   # Error message to stderr.
  12   exit $NOARGS
  13   # Returns 1 as exit status of script
  14   # (error code)
  15 fi
  16
  17 filename=$1
  18
  19 if [ ! -f $filename ]
  20 then
  21   echo "File $filename not found!" >&2
  22   # Error message to stderr.
  23   exit 2
  24 fi
  25
  26 if [ ${filename##*.} != "gz" ]
  27 # Using bracket in variable substitution.
  28 then
  29   echo "File $1 is not a gzipped file!"
  30   exit 3
  31 fi
  32
  33 zcat $1 | most
  34
  35 exit 0
  36
  37 # Uses the file viewer 'most'
  38 # (similar to 'less')


Top Visited
Switchboard
Latest
Past week
Past month

NEWS CONTENTS

Old News ;-)

[Aug 19, 2017] Checking shell options

To add in your Bash configuration files:

# These lines will print a message if the noclobber option is set:

if [ -o noclobber ]
  then
	echo "Your files are protected against accidental overwriting using redirection."
fi

Top Visited
Switchboard
Latest
Past week
Past month

NEWS CONTENTS

Old News ;-)

[Nov 04, 2016] Coding Style rear-rear Wiki

Reading rear sources is an interesting exercise. It really demonstrates attempt to use "reasonable' style of shell programming and you can learn a lot.
Nov 04, 2016 | github.com

Relax-and-Recover is written in Bash (at least bash version 3 is needed), a language that can be used in many styles. We want to make it easier for everybody to understand the Relax-and-Recover code and subsequently to contribute fixes and enhancements.

Here is a collection of coding hints that should help to get a more consistent code base.

Don't be afraid to contribute to Relax-and-Recover even if your contribution does not fully match all this coding hints. Currently large parts of the Relax-and-Recover code are not yet in compliance with this coding hints. This is an ongoing step by step process. Nevertheless try to understand the idea behind this coding hints so that you know how to break them properly (i.e. "learn the rules so you know how to break them properly").

The overall idea behind this coding hints is:

Make yourself understood

Make yourself understood to enable others to fix and enhance your code properly as needed.

From this overall idea the following coding hints are derived.

For the fun of it an extreme example what coding style should be avoided:

#!/bin/bash for i in `seq 1 2 $((2*$1-1))`;do echo $((j+=i));done



   

Try to find out what that code is about - it does a useful thing.

Code must be easy to read Code should be easy to understand

Do not only tell what the code does (i.e. the implementation details) but also explain what the intent behind is (i.e. why ) to make the code maintainable.

Here the initial example so that one can understand what it is about:

#!/bin/bash # output the first N square numbers # by summing up the first N odd numbers 1 3 ... 2*N-1 # where each nth partial sum is the nth square number # see https://en.wikipedia.org/wiki/Square_number#Properties # this way it is a little bit faster for big N compared to # calculating each square number on its own via multiplication N=$1 if ! [[ $N =~ ^[0-9]+$ ]] ; then echo "Input must be non-negative integer." 1>&2 exit 1 fi square_number=0 for odd_number in $( seq 1 2 $(( 2 * N - 1 )) ) ; do (( square_number += odd_number )) && echo $square_number done

Now the intent behind is clear and now others can easily decide if that code is really the best way to do it and easily improve it if needed.

Try to care about possible errors

By default bash proceeds with the next command when something failed. Do not let your code blindly proceed in case of errors because that could make it hard to find the root cause of a failure when it errors out somewhere later at an unrelated place with a weird error message which could lead to false fixes that cure only a particular symptom but not the root cause.

Maintain Backward Compatibility

Implement adaptions and enhancements in a backward compatible way so that your changes do not cause regressions for others.

Dirty hacks welcome

When there are special issues on particular systems it is more important that the Relax-and-Recover code works than having nice looking clean code that sometimes fails. In such special cases any dirty hacks that intend to make it work everywhere are welcome. But for dirty hacks the above listed coding hints become mandatory rules:

For example a dirty hack like the following is perfectly acceptable:

# FIXME: Dirty hack to make it work # on "FUBAR Linux version 666" # where COMMAND sometimes inexplicably fails # but always works after at most 3 attempts # see http://example.org/issue12345 # Retries should have no bad effect on other systems # where the first run of COMMAND works. COMMAND || COMMAND || COMMAND || Error "COMMAND failed."

Character Encoding

Use only traditional (7-bit) ASCII charactes. In particular do not use UTF-8 encoded multi-byte characters.

Text Layout Variables Functions Relax-and-Recover functions

Use the available Relax-and-Recover functions when possible instead of re-implementing basic functionality again and again. The Relax-and-Recover functions are implemented in various lib/*-functions.sh files .

test, [, [[, (( Paired parenthesis See also

[May 02, 2015] ArithmeticExpression

2014-02-13 | Greg's Wiki

Arithmetic in BASH is integer math only. You can't do floating point math in Bash; if you need that capability, see Bash FAQ #22.

Also see the Bash hackers article about the full syntax theory. /!\ The $[ ] syntax is deprecated

There are several ways to tell Bash to treat numbers as integers instead of strings, and to do basic arithmetic operations on them. The first is to use the let command:

let a=17+23
echo "a = $a"      # Prints a = 40

Note that each arithmetic expression has to be passed as a single argument to the let command, so you need quotes if there are spaces or globbing characters, thus:

let a=17 + 23      # WRONG
let a="17 + 23"    # Right
let 'a = 17 + 23'  # Right
let a=17 a+=23     # Right (2 arithmetic expressions)

let a[1]=1+1       # Wrong (try after touch a1=1+1 or with shopt -s failglob)
let 'a[1]=1+1'     # Right
let a\[1]=1+1      # Right

Division in Bash is integer division, and it truncates the results, just as in C:

let a=28/6
echo "a = $a"      # Prints a = 4

In addition to the let command, one may use the (( )) syntax to enforce an arithmetic context. If there is a $ (dollar sign) before the parentheses, then a substitution is performed (more on this below). White space is allowed inside (( )) with much greater leniency than with let, and variables inside (( )) don't require $ (because string literals aren't allowed). Examples:

((a=$a+7))         # Add 7 to a
((a = a + 7))      # Add 7 to a.  Identical to the previous command.
((a += 7))         # Add 7 to a.  Identical to the previous command.

((a = RANDOM % 10 + 1))     # Choose a random number from 1 to 10.
                            # % is modulus, as in C.

# (( )) may also be used as a command.  > or < inside (( )) means
# greater/less than, not output/input redirection.
if ((a > 5)); then echo "a is more than 5"; fi

(( )) without the leading $ is not a standard sh feature. It comes from ksh and is only available in ksh, Bash and zsh. $(( )) substitution is allowed in the POSIX shell. As one would expect, the result of the arithmetic expression inside the $(( )) is substituted into the original command. Like for parameter substitution, arithmetic substitution is subject to word splitting so should be quoted to prevent it when in list contexts. Here are some examples of the use of the arithmetic substitution syntax:

a=$((a+7))         # POSIX-compatible version of previous code.
if test "$((a%4))" = 0; then ...
lvcreate -L "$((4*1096))" -n lvname vgname   # Actual HP-UX example.

Variables may be declared as integers so that any subsequent assignments to them will always assume a numeric context. Essentially any variable that's declared as an integer acts as if you had a let command in front of it when you assign to it. For example:

unset b             # Forget any previous declarations
b=7+5; echo "$b"    # Prints 7+5
declare -i b        # Declare b as an integer
b=7+5; echo "$b"    # Prints 12

Also, array indices are a numeric context:

n=0
while read line; do
   array[n++]=$line      # array[] forces a numeric context
done

There is one common pitfall with arithmetic expressions in Bash: numbers with leading zeroes are treated as octal. For example,

# Suppose today is September 19th.
month=$(date +%m)
next_month=$(( (month == 12) ? 1 : month+1 ))
# bash: 09: value too great for base (error token is "09")

This causes great confusion among people who are extracting zero-padded numbers from various sources (although dates are by far the most common) and then doing math on them without sanitizing them first. (It's especially bad if you write a program like this in March, test it, roll it out... and then it doesn't blow up until August 1.)

If you have leading-zero problems with Bash's built-in arithmetic, there are two possible solutions. The first is, obviously, to remove the leading zeroes from the numbers before doing math with them. This is not trivial in Bash, unfortunately, because Bash has no ability to perform substitutions on a variable using regular expressions (it can only do it with "glob" patterns). But you could use a loop:

# This removes leading zeroes from a, one at a time.
while [[ $a = 0* ]]; do a=${a#0}; done

You can do the above without using a loop, by using extended globs; see FAQ #67 for more information. Or, you could use sed; that may be more efficient if you're reading many numbers from a stream, and can arrange to sanitize them all in one command, rather than one by one.

Without a loop:

# This removes leading zeroes from a, all at once.
a=${a##+(0)}

The third solution is to force Bash to treat all numbers as base 10 by prefixing them with 10#. This might be more efficient, but also may be less elegant to read.

a=008
let b=a+1       # Generates an error because 008 is not valid in octal.
let b=10#$a+1   # Force a to be treated as base 10.  Note: the $ is required.

Finally, a note on the exit status of commands, and the notions of "true" and "false", is in order. When bash runs a command, that command will return an exit status from 0 to 255. 0 is considered "success" (which is "true" when used in the context of an if or while command). However, in an arithmetic context, there are places where the C language rules (0 is false, anything else is true) apply.

Some examples:

true; echo "$?"       # Writes 0, because a successful command returns 0.
((10 > 6)); echo "$?" # Also 0.  An arithmetic command returns 0 for true.
echo "$((10 > 6))"    # Writes 1.  An arithmetic expression returns 1 for true.

In addition to a comparison returning 1 for true, an arithmetic expression that evaluates to a non-zero value is also true in the sense of a command.

if ((1)); then echo true; fi     # Writes true.

This also lets you use "flag" variables, just like in a C program:

found=0
while ...; do
   ...
  if something; then found=1; fi    # Found one!  Keep going.
   ...
done
if ((found)); then ...

Here is a function to convert numbers in other bases to decimal (base 10):

todec() {
    echo "$(( $1#$2 ))"
}

Examples:

todec 16 ffe    # -> 4094
todec 2 100100  # -> 36

Recommended Links

Softpanorama hot topic of the month

Softpanorama Recommended

Reference

compound comparison

-a
logical and

exp1 -a exp2 returns true if both exp1 and exp2 are true.

-o
logical or

exp1 -o exp2 returns true if either exp1 or exp2 are true.

These are simpler forms of the comparison operators && and ||, which require brackets to separate the target expressions.

Extended Set of File Test Operators (Bash only)

In bash we have extended set of file test operators:

-e
file exists
-f
file is a regular file
-s
file is not zero size
-d
file is a directory
-b
file is a block device (floppy, cdrom, etc.)
-c
file is a character device (keyboard, modem, sound card, etc.)
-p
file is a pipe
-L
file is a symbolic link
-S
file is a socket
-r
file is readable (has read permission)
-w
file has write permission
-x
file has execute permission
-g
group-id flag set on file
-u
user-id flag set on file
-k
"sticky bit" set (if user does not own a directory that has the sticky bit set, she cannot delete files in it, not even files she owns)
-O
you are owner of file
-G
group-id of file same as yours
-t n
file descriptor n is open

This usually refers to stdin, stdout, and stderr (file descriptors 0 - 2).

f1 -nt f2
file f1 is newer than f2
f1 -ot f2
file f1 is older than f2
f1 -ef f2
files f1 and f2 are links to the same file
!
"not" -- reverses the sense of the tests above (returns true if condition absent).

Comparison operators (binary) got [...] and [[...]] conditional expressions

integer comparison

-eq
is equal to ($a -eq $b)
-ne
is not equal to ($a -ne $b)
-gt
is greater than ($a -gt $b)
-ge
is greater than or equal to ($a -ge $b)
-lt
is less than ($a -lt $b)
-le
is less than or equal to ($a -le $b)

string comparison

=
is equal to ($a = $b)
!=
is not equal to ($a != $b)
\<
is less than, in ASCII alphabetical order ($a \< $b)

Note that the "<" needs to be escaped.

\>
is greater than, in ASCII alphabetical order ($a \> $b)

Note that the ">" needs to be escaped.

See Example 3-91 for an application of this comparison operator.

-z
string is "null", that is, has zero length
-n
string is not "null".
This test requires that the string be quoted within the test brackets. You may use ! -z instead, or even just the string itself, without a test operator (see Example 3-13).


Etc

FAIR USE NOTICE This site contains copyrighted material the use of which has not always been specifically authorized by the copyright owner. We are making such material available in our efforts to advance understanding of environmental, political, human rights, economic, democracy, scientific, and social justice issues, etc. We believe this constitutes a 'fair use' of any such copyrighted material as provided for in section 107 of the US Copyright Law. In accordance with Title 17 U.S.C. Section 107, the material on this site is distributed without profit exclusivly for research and educational purposes.   If you wish to use copyrighted material from this site for purposes of your own that go beyond 'fair use', you must obtain permission from the copyright owner. 

ABUSE: IPs or network segments from which we detect a stream of probes might be blocked for no less then 90 days. Multiple types of probes increase this period.  

Society

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

Quotes

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

Bulletin:

Vol 25, No.12 (December, 2013) Rational Fools vs. Efficient Crooks The efficient markets hypothesis : Political Skeptic Bulletin, 2013 : Unemployment Bulletin, 2010 :  Vol 23, No.10 (October, 2011) An observation about corporate security departments : Slightly Skeptical Euromaydan Chronicles, June 2014 : Greenspan legacy bulletin, 2008 : Vol 25, No.10 (October, 2013) Cryptolocker Trojan (Win32/Crilock.A) : Vol 25, No.08 (August, 2013) Cloud providers as intelligence collection hubs : Financial Humor Bulletin, 2010 : Inequality Bulletin, 2009 : Financial Humor Bulletin, 2008 : Copyleft Problems Bulletin, 2004 : Financial Humor Bulletin, 2011 : Energy Bulletin, 2010 : Malware Protection Bulletin, 2010 : Vol 26, No.1 (January, 2013) Object-Oriented Cult : Political Skeptic Bulletin, 2011 : 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.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-2016 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.

The site uses AdSense so you need to be aware of Google privacy policy. You you do not want to be tracked by Google please disable Javascript for this site. This site is perfectly usable without Javascript.

Original materials copyright belong to respective owners. Quotes are made for educational purposes only in compliance with the fair use doctrine.

FAIR USE NOTICE This site contains copyrighted material the use of which has not always been specifically authorized by the copyright owner. We are making such material available to advance understanding of computer science, IT technology, economic, scientific, and social issues. We believe this constitutes a 'fair use' of any such copyrighted material as provided by section 107 of the US Copyright Law according to which such material can be distributed without profit exclusively for research and educational purposes.

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 development of this site and speed up access. In case softpanorama.org is down you can use the at softpanorama.info

Disclaimer:

The statements, views and opinions presented on this web page are those of the author (or referenced source) 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: August 30, 2017