Advanced Bash-Scripting Guide Looping and Branching - AkuCode

Advanced Bash-Scripting Guide Looping and Branching

        At the heart of any programming language are iteration and conditional execution. Iteration is the repetition of a section of code until a condition changes. Conditional execution is making a choice between two or more actions (one of which may be to do nothing) based on a condition.
        In the shell, there are there types of the loop (while, until, and for) and three types of conditional execution (if, case, and the conditional operators && and ||, which mean AND and OR, respectively). With the exception of for and case, the exit status of command controls the behavior.

Exit Status

        You can test the success of a command directly using the shell keywords while, until, and if or with the control operators && and ||. The exit code is stored in the special parameter $?.
        If the command can be executed successfully (or true), the value of $? is zero. If the command failed for some reason, $? will contain a positive integer between I and 255 inclusive. A failed command usually returns 1. Zero and nonzero exit codes are also known as true-false, respectively.
        A command may failed because of a syntax error:
$ printf "%v\n"
bash: printf: `v': invalid format character
$ echo $? 
}

    Alternatively, failure may be the result of the command not being able to accomplish it task:
$ mkdir /qwerty
bash: mkdir: cannot create directory `/qwerty': Permission denied
$ echo $? 

Testing an Expression

        Expressions are deemed to be true or false by the test command or one of two nonstandard shell reserved words, [[ and ((. The best command compares strings, integers, and various file attributes; (( test arithmetic expressions and [[ ... ]] does the same as the test with the additional feature of comparing regular expressions.

test, aka [ ... ]

        The test command evaluates many kinds of expressions, from file properties to integers to strings. It is a built-in command, and therefore its arguments are expanded just as for any other command. (See Chapter 5 for more information.) The alternative version ([) requires a closing bracket at the end.

File Test

        Several operators test the state of a file. A file's existence can be tested with -e (or the nonstandard -a). The type of file can be checked with -f for a regular file, -d for a directory, and -h or -L for a symbolic link. Other operators test for a special type of files and for which permission bits are set.
        Here are some examples:
test -f /etc/fstab ## true if a regular file
test -h /etc/rc.local ## true if a symbolic link
[ -x $HOME/bin/hw ] ## true if you can execute the file 

Integer Test

        Comparisons between integers use the -eq, -ne, -gt, -lt, -ge, and -le operators. The equality of integers is tested with -eq:
$ test 1 -eq 1
$ echo $?
0
$ [ 2 -eq 1 ]
$ echo $?

        Inequality is tested with -ne:
$ [ 2 -ne 1 ]
$ echo $?
0 

The remaining operators' test was greater than, less than, greater than or equal to, and less than or equal to.

String Tests

        String are concatenations of zero or more characters and can include any character except NULL (ASCII 0). They can be tested for equality, for non-empty string or null string, and in bash for alphabetical ordering. The = operator test for equality, in other words, whether they are identical; != test for inequality.
        Here are some examples:
test "$a" = "$b"
[ "$q" != "$b" ] 

        The -z and -n operators return successfully if their arguments are empty or non-empty:
$ [ -z "" ]
$ echo $?
0
$ test -n ""
$ echo $?
1 

        The greater-than and less-than symbols are used in bash to compare the lexical positions of strings and must be escaped to prevent them from being interpreted as redirection operators:
$ str1=abc
$ str2=def
$ test "$str1" \< "$str2"
$ echo $?
0
$ test "$str1" \> "$str2"
$ echo $?
1

        The previous test can be combined in a single call to test with the -a (logical AND) and -o (logical OR) operators:
test -f /path/to/file -a $test -eq 1
test -x bin/file -o $test -gt 1 

        test is usually used in combination with if or the conditional operators && and ||.

[[ ... ]]: Evaluate an Expression

        Like test, [[ ... ]] evaluates an expression. Unlike tests, it is not a built-in command. It is part of the shell grammar and not subject to the same parsing as a built-in command. Parameters are expanded, but word splitting and filename expansion are not performed on words between [[ and ]]. It supports all the same operators as a test, with some enhancements and additions. It is, however, nonstandard, so it is better not to use it when the test could perform the same function.

Enhancements over Test

        When the argument to the right of = or != is unquote, it is created as a pattern and duplicates the functionality of the case command. The feature of [[ ... ]] that is not duplicated elsewhere in the shell is the ability to match and extended regular expression using the =~ operator:
$ string=whatever
$ [[ $string =~ h[aeiou] ]]
$ echo $?
0 
$ [[ $string =~ h[sdfghjkl] ]]
$ echo $?
1 

(( ... )): Evaluate an Arithmetic Expression

        A nonstandard feature, (( Arithmetic expression )) returns false if the arithmetic expression evaluates to zero and returns true otherwise. The portable equivalent uses test and the POSIX syntax for shell arithmetic:
test $(( $a - 2 )) -ne 0 

Lists

        A list is a sequence of one or more commands separated by semicolons, ampersands, control operators, or newline. A list may be used as the condition in a while or until loop or as the body of any loop. The exit code of a list is the exit code of the last command in the list.

Conditional execution

        Conditional constructs enable a script to decide whether to execute a block of code or to select which of two or more blocks to execute.

if

        The basic if command evaluates a list of one or more commands and executes a list if the execution of <condition list> is successful:
if < condition list >
then
    < list >
fi 

        Usually, the < condition list > is a single command, veryf often test or its synonym, [. the -z operand to test checks whether a name was entered
read name
if [ -z "$name" ]
then
  echo "No name entered" >&2
  exit 1 ## Set a failed return code
fi 

        Using the else keyword, a different set of commands can be executed if the < condition list > fails, as shown here :
printf "Enter a number no greater than 10: "
read number
if [ "$number" -gt 10 ]
then
   printf "%d is too big\n" "$number" >&2
   exit 1
else
   printf "You entered %d\n" "$number"
fi 

        More than one condition can be given, using the elif keyword, so that if the first test fails, the second is tried, as shown:
printf "Enter a number between 10 and 20 inclusive: "
read number
if [ "$number" -lt 10 ]
then
   printf "%d is too low\n" "$number" >&2
   exit 1
elif [ "$number" -gt 20 ]
then
   printf "%d is too high\n" "$number" >&2
   exit 1
else
   printf "You entered %d\n" "$number"
fi 


Often more than one test is given in the < condition list > using && and ||.

Condition Operators, && and ||

        Lists containing the AND and OR conditional operators are evaluated from left to right. A command following the AND operator (&&) is executed if the previous command is successful. The part following the OR operator (||) is executed if the previous command fails.
        For example, to check for a directory and cd into it if it exist, use this:
test -d "$directory" && cd "$directory"

        To change directory and exit with an error if cd fails, use this:
cd "$HOME/bin" || exit 1

        The next command tries to create a directory and cd do it. If either mkdir or cd fails, it exist with an error:
mkdir "$HOME/bin" && cd "$HOME/bin" || exit 1

        Conditional operators are often used with if. In this example, the echo command is executed if both test are successful:
if [ -d "$dir" ] && cd "$dir"
then
   echo "$PWD"
fi

case

        A case statement compares a word (usually a variable) against one or more patterns and executes the commands associated with that pattern. The patterns are pathname expansion patterns using wildcards (* and ?) and character lists and ranges ([ ... ]). The syntax is as follows:
case WORD in
   PATTERN) COMMANDS ;;
   PATTERN) COMMANDS ;; ## optional
esac

        A common use of case is to determine whether one string is contained in another. It is much faster than using grep, which creates a new process. This short script would normally be implemented as a shell function (see Chapter 6) so that it will be executed without creating a new process.
case $1 in
   *"$2"*) true;;
   *) false ;;
esac

        The commands, true and false, do nothing but succeed or fail, repectively. Another common task is to check whether a string is a valid number.
case $1 in
   *[!0-9]*) false;;
   *) true ;;
esac

        Many scripts require one or more arguments on the command line. To check whether there are the correct number, the case is often used:
case $# in
    3) ;; ## we need 3 args, so do nothing
    *) printf "%s\n" "Please provide three names" >&2
    exit 1
    ;;
esac

Looping

        When a command or series of commands needs to be repeated, it is put inside a loop. The shell provides three types of loop:while,until,and for. The First two execute until a condition is either true or false; the third loops through a list of words.

while

        The condition for a while loop is a list of one or more commands, and the commands to be executed while the condition remains true are placed between the keywords do and done:
while < list >
do
   < list >
done

        By incrementing a variable each time the loop is executed, the commands can be run a specific number of times:
n=1
while [ $n -le 10 ]
do
   echo "$n"
   n=$(( $n + 1 ))
done

        The true command can be used to create an infinite loop:
while true ## : can be used in place of true
do
   read x
done

    A while loop can be used to read line by line from a file:
while IFS= read -r line
do
    : do something with "$line"
done

until

        Rarely used, until loops as long as the condition fails. It is the opposite of while:
n=1
until [ $n -gt 10 ]
do
    echo "$n"
    n=$(( $n + 1 ))
done

for<    At the top of a for loop, a variable is given a value from a list of words. On each iteration, the next word in the list is assigned:
for var in Canada USA Mexico
do
    printf "%s\n" "$var"
done

        bash also has a nonstandard form that is similar to that found in the C programming language. The first expression is evaluated when first encountered. The second is a test. The third is evaluated after each iteration:
for (( n=1; n<=10; ++n ))
do
   echo "$n"
done

Since this offers no advantage over standard looping methods, it is not used in this book.
break
        A loop can be exited at any point with the break command:
while :
do
   read x
   [ -z "$x" ] && break
done

        With a numeric argument, a break can exit multiple nested loops:
for n in a b c d e
do
   while true
   do
   if [ $RANDOM -gt 20000 ]
   then
   printf .
   break 2 ## break out of both while and for loops
   elif [ $RANDOM -lt 10000 ]
   then
   printf '"'
   break ## break out of the while loop
   fi
   done
done

Continue

        Inside a loop, the continue command immediately starts a new iteration of the loop, bypassing any remaining commands:
for n in {1..9} ## See Brace expansion in Chapter 4
do
   x=$RANDOM
   [ $x -le 20000 ] && continue
   echo "n=$n x=$x"
done

Summary

        Looping and branching are major building blocks of a computer program. In this chapter, you learned the commands and operators used for these tasks.

Commands

  • test: Evaluates an expression and returns success or failure.
  • if: Executes a set of command if a list of commands is successful and optionally executes a different set if it is not.
  •  case: Matches a word with one or more patterns and executes the commands associated with the first matching pattern.
  • while: Repeatedly executes a set of commands while a list of commands executes successfully.
  • until: Repeatedly executes a set of commands until a list of commands execute successfully.
  • for: Repeatedly executes a set of commands for each word in a list.
  • break: Exits from a loop.
  • continue: Starts the next iteration of a loop immediately.

Concepts 

  • Exit status: The success or failure of command, stored as 0 or a positive integer in the special parameter $?
  •  List: A sequence of one or more commands separated by;, &, &&, ||, or a newline
Comments