Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting
Prev |  | Next

# Chapter 26. 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.

Chaining together commands

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` (zero). At the first `false` (non-zero) return, the command chain terminates (the first command returning `false` is the last one to execute).

  An interesting use of a two-condition and list from an early version of YongYe's Tetris game script:

         1 equation()
         2
         3 {  # core algorithm used for doubling and halving the coordinates
         4    [[ ${cdx} ]] && ((y=cy+(ccy-cdy)${2}2))
         5    eval ${1}+=\"${x} ${y} \"
         6 }

  Example 26-1. Using an and list to test for command-line arguments

         1 #!/bin/bash
         2 # and list
         3
         4 if [ ! -z "$1" ] && echo "Argument #1 = $1" && [ ! -z "$2" ] && \
         5 #                ^^                         ^^               ^^
         6 echo "Argument #2 = $2"
         7 then
         8   echo "At least 2 arguments passed to script."
         9   # All the chained commands return true.
        10 else
        11   echo "Fewer than 2 arguments passed to script."
        12   # At least one of the chained commands returns false.
        13 fi
        14 # Note that "if [ ! -z $1 ]" works, but its alleged equivalent,
        15 #   "if [ -n $1 ]" does not.
        16 #     However, quoting fixes this.
        17 #  if "[ -n "$1" ]" works.
        18 #           ^  ^    Careful!
        19 # It is always best to QUOTE the variables being tested.
        20
        21
        22 # This accomplishes the same thing, using "pure" if/then statements.
        23 if [ ! -z "$1" ]
        24 then
        25   echo "Argument #1 = $1"
        26 fi
        27 if [ ! -z "$2" ]
        28 then
        29   echo "Argument #2 = $2"
        30   echo "At least 2 arguments passed to script."
        31 else
        32   echo "Fewer than 2 arguments passed to script."
        33 fi
        34 # It's longer and more ponderous than using an "and list".
        35
        36
        37 exit $?

  Example 26-2. Another command-line arg test using an and list

         1 #!/bin/bash
         2
         3 ARGS=1        # Number of arguments expected.
         4 E_BADARGS=85  # Exit value if incorrect number of args passed.
         5
         6 test $# -ne $ARGS && \
         7 #    ^^^^^^^^^^^^ condition #1
         8 echo "Usage: `basename $0` $ARGS argument(s)" && exit $E_BADARGS
         9 #                                             ^^
        10 #  If condition #1 tests true (wrong number of args passed to script),
        11 #+ then the rest of the line executes, and script terminates.
        12
        13 # Line below executes only if the above test fails.
        14 echo "Correct number of arguments passed to this script."
        15
        16 exit 0
        17
        18 # To check exit value, do a "echo $?" after script termination.

  Of course, an and list can also set variables to a default value.

         1 arg1=$@ && [ -z "$arg1" ] && arg1=DEFAULT
         2
         3               # Set $arg1 to command-line arguments, if any.
         4               # But . . . set to DEFAULT if not specified on command-line.

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 26-3. Using or lists in combination with an and list

         1 #!/bin/bash
         2
         3 #  delete.sh, a not-so-cunning file deletion utility.
         4 #  Usage: delete filename
         5
         6 E_BADARGS=85
         7
         8 if [ -z "$1" ]
         9 then
        10   echo "Usage: `basename $0` filename"
        11   exit $E_BADARGS  # No arg? Bail out.
        12 else
        13   file=$1          # Set filename.
        14 fi
        15
        16
        17 [ ! -f "$file" ] && echo "File \"$file\" not found. \
        18 Cowardly refusing to delete a nonexistent file."
        19 # AND LIST, to give error message if file not present.
        20 # Note echo message continuing on to a second line after an escape.
        21
        22 [ ! -f "$file" ] || (rm -f $file; echo "File \"$file\" deleted.")
        23 # OR LIST, to delete file if present.
        24
        25 # Note logic inversion above.
        26 # AND LIST executes on true, OR LIST on false.
        27
        28 exit $?

  | If the first command in an or list returns true, it `will` execute.

       1 # ==> The following snippets from the /etc/rc.d/init.d/single
       2 #+==> script by Miquel van Smoorenburg
       3 #+==> illustrate use of "and" and "or" lists.
       4 # ==> "Arrowed" comments added by document author.
       5
       6 [ -x /usr/bin/clear ] && /usr/bin/clear
       7   # ==> If /usr/bin/clear exists, then invoke it.
       8   # ==> Checking for the existence of a command before calling it
       9   #+==> avoids error messages and other awkward consequences.
      10
      11   # ==> . . .
      12
      13 # If they want to run something in single user mode, might as well run it...
      14 for i in /etc/rc1.d/S[0-9][0-9]* ; do
      15         # Check if the script is there.
      16         [ -x "$i" ] || continue
      17   # ==> If corresponding file in $PWD *not* found,
      18   #+==> then "continue" by jumping to the top of the loop.
      19
      20         # Reject backup files and files generated by rpm.
      21         case "$1" in
      22                 *.rpmsave|*.rpmorig|*.rpmnew|*~|*.orig)
      23                         continue;;
      24         esac
      25         [ "$i" = "/etc/rc1.d/S00single" ] && continue
      26   # ==> Set script name, but don't execute it yet.
      27         $i start
      28 done
      29
      30   # ==> . . .

| The exit status of an `and list` or an `or list` is the exit status of the last command executed.

Clever combinations of and and or lists are possible, but the logic may easily become convoluted and require close attention to operator precedence rules, and possibly extensive debugging.

       1 false && true || echo false         # false
       2
       3 # Same result as
       4 ( false && true ) || echo false     # false
       5 # But NOT
       6 false && ( true || echo false )     # (nothing echoed)
       7
       8 #  Note left-to-right grouping and evaluation of statements.
       9
      10 #  It's usually best to avoid such complexities.
      11
      12 #  Thanks, S.C.

See Example A-7 and Example 7-4 for illustrations of using `and / or list` constructs to test variables.

Prev | Home | Next
Aliases | Up | Arrays