Chapter 37. Bash, versions 2, 3, and 4

37.1. Bash, version 2

The current version of Bash, the one you have running on your machine, is most likely version 2.xx.yy, 3.xx.yy, or 4.xx.yy.
 bash$ echo $BASH_VERSION
 3.2.25(1)-release
 	      

The version 2 update of the classic Bash scripting language added array variables, string and parameter expansion, and a better method of indirect variable references, among other features.


Example 37-1. String expansion

   1 #!/bin/bash
   2 
   3 # String expansion.
   4 # Introduced with version 2 of Bash.
   5 
   6 #  Strings of the form $'xxx'
   7 #+ have the standard escaped characters interpreted. 
   8 
   9 echo $'Ringing bell 3 times \a \a \a'
  10      # May only ring once with certain terminals.
  11      # Or ...
  12      # May not ring at all, depending on terminal settings.
  13 echo $'Three form feeds \f \f \f'
  14 echo $'10 newlines \n\n\n\n\n\n\n\n\n\n'
  15 echo $'\102\141\163\150'
  16      #   B   a   s   h
  17      # Octal equivalent of characters.
  18 
  19 exit


Example 37-2. Indirect variable references - the new way

   1 #!/bin/bash
   2 
   3 # Indirect variable referencing.
   4 # This has a few of the attributes of references in C++.
   5 
   6 
   7 a=letter_of_alphabet
   8 letter_of_alphabet=z
   9 
  10 echo "a = $a"           # Direct reference.
  11 
  12 echo "Now a = ${!a}"    # Indirect reference.
  13 #  The ${!variable} notation is more intuitive than the old
  14 #+ eval var1=\$$var2
  15 
  16 echo
  17 
  18 t=table_cell_3
  19 table_cell_3=24
  20 echo "t = ${!t}"                      # t = 24
  21 table_cell_3=387
  22 echo "Value of t changed to ${!t}"    # 387
  23 # No 'eval' necessary.
  24 
  25 #  This is useful for referencing members of an array or table,
  26 #+ or for simulating a multi-dimensional array.
  27 #  An indexing option (analogous to pointer arithmetic)
  28 #+ would have been nice. Sigh.
  29 
  30 exit 0
  31 
  32 # See also, ind-ref.sh example.


Example 37-3. Simple database application, using indirect variable referencing

   1 #!/bin/bash
   2 # resistor-inventory.sh
   3 # Simple database / table-lookup application.
   4 
   5 # ============================================================== #
   6 # Data
   7 
   8 B1723_value=470                                   # Ohms
   9 B1723_powerdissip=.25                             # Watts
  10 B1723_colorcode="yellow-violet-brown"             # Color bands
  11 B1723_loc=173                                     # Where they are
  12 B1723_inventory=78                                # How many
  13 
  14 B1724_value=1000
  15 B1724_powerdissip=.25
  16 B1724_colorcode="brown-black-red"
  17 B1724_loc=24N
  18 B1724_inventory=243
  19 
  20 B1725_value=10000
  21 B1725_powerdissip=.125
  22 B1725_colorcode="brown-black-orange"
  23 B1725_loc=24N
  24 B1725_inventory=89
  25 
  26 # ============================================================== #
  27 
  28 
  29 echo
  30 
  31 PS3='Enter catalog number: '
  32 
  33 echo
  34 
  35 select catalog_number in "B1723" "B1724" "B1725"
  36 do
  37   Inv=${catalog_number}_inventory
  38   Val=${catalog_number}_value
  39   Pdissip=${catalog_number}_powerdissip
  40   Loc=${catalog_number}_loc
  41   Ccode=${catalog_number}_colorcode
  42 
  43   echo
  44   echo "Catalog number $catalog_number:"
  45   # Now, retrieve value, using indirect referencing.
  46   echo "There are ${!Inv} of  [${!Val} ohm / ${!Pdissip} watt]\
  47   resistors in stock."  #        ^             ^
  48   # As of Bash 4.2, you can replace "ohm" with \u2126 (using echo -e).
  49   echo "These are located in bin # ${!Loc}."
  50   echo "Their color code is \"${!Ccode}\"."
  51 
  52   break
  53 done
  54 
  55 echo; echo
  56 
  57 # Exercises:
  58 # ---------
  59 # 1) Rewrite this script to read its data from an external file.
  60 # 2) Rewrite this script to use arrays,
  61 #+   rather than indirect variable referencing.
  62 #    Which method is more straightforward and intuitive?
  63 #    Which method is easier to code?
  64 
  65 
  66 # Notes:
  67 # -----
  68 #  Shell scripts are inappropriate for anything except the most simple
  69 #+ database applications, and even then it involves workarounds and kludges.
  70 #  Much better is to use a language with native support for data structures,
  71 #+ such as C++ or Java (or even Perl).
  72 
  73 exit 0


Example 37-4. Using arrays and other miscellaneous trickery to deal four random hands from a deck of cards

   1 #!/bin/bash
   2 # cards.sh
   3 
   4 # Deals four random hands from a deck of cards.
   5 
   6 UNPICKED=0
   7 PICKED=1
   8 
   9 DUPE_CARD=99
  10 
  11 LOWER_LIMIT=0
  12 UPPER_LIMIT=51
  13 CARDS_IN_SUIT=13
  14 CARDS=52
  15 
  16 declare -a Deck
  17 declare -a Suits
  18 declare -a Cards
  19 #  It would have been easier to implement and more intuitive
  20 #+ with a single, 3-dimensional array.
  21 #  Perhaps a future version of Bash will support multidimensional arrays.
  22 
  23 
  24 initialize_Deck ()
  25 {
  26 i=$LOWER_LIMIT
  27 until [ "$i" -gt $UPPER_LIMIT ]
  28 do
  29   Deck[i]=$UNPICKED   # Set each card of "Deck" as unpicked.
  30   let "i += 1"
  31 done
  32 echo
  33 }
  34 
  35 initialize_Suits ()
  36 {
  37 Suits[0]=C #Clubs
  38 Suits[1]=D #Diamonds
  39 Suits[2]=H #Hearts
  40 Suits[3]=S #Spades
  41 }
  42 
  43 initialize_Cards ()
  44 {
  45 Cards=(2 3 4 5 6 7 8 9 10 J Q K A)
  46 # Alternate method of initializing an array.
  47 }
  48 
  49 pick_a_card ()
  50 {
  51 card_number=$RANDOM
  52 let "card_number %= $CARDS" # Restrict range to 0 - 51, i.e., 52 cards.
  53 if [ "${Deck[card_number]}" -eq $UNPICKED ]
  54 then
  55   Deck[card_number]=$PICKED
  56   return $card_number
  57 else  
  58   return $DUPE_CARD
  59 fi
  60 }
  61 
  62 parse_card ()
  63 {
  64 number=$1
  65 let "suit_number = number / CARDS_IN_SUIT"
  66 suit=${Suits[suit_number]}
  67 echo -n "$suit-"
  68 let "card_no = number % CARDS_IN_SUIT"
  69 Card=${Cards[card_no]}
  70 printf %-4s $Card
  71 # Print cards in neat columns.
  72 }
  73 
  74 seed_random ()  # Seed random number generator.
  75 {               # What happens if you don't do this?
  76 seed=`eval date +%s`
  77 let "seed %= 32766"
  78 RANDOM=$seed
  79 } # Consider other methods of seeding the random number generator.
  80 
  81 deal_cards ()
  82 {
  83 echo
  84 
  85 cards_picked=0
  86 while [ "$cards_picked" -le $UPPER_LIMIT ]
  87 do
  88   pick_a_card
  89   t=$?
  90 
  91   if [ "$t" -ne $DUPE_CARD ]
  92   then
  93     parse_card $t
  94 
  95     u=$cards_picked+1
  96     # Change back to 1-based indexing, temporarily. Why?
  97     let "u %= $CARDS_IN_SUIT"
  98     if [ "$u" -eq 0 ]   # Nested if/then condition test.
  99     then
 100      echo
 101      echo
 102     fi                  # Each hand set apart with a blank line.
 103 
 104     let "cards_picked += 1"
 105   fi  
 106 done  
 107 
 108 echo
 109 
 110 return 0
 111 }
 112 
 113 
 114 # Structured programming:
 115 # Entire program logic modularized in functions.
 116 
 117 #===============
 118 seed_random
 119 initialize_Deck
 120 initialize_Suits
 121 initialize_Cards
 122 deal_cards
 123 #===============
 124 
 125 exit
 126 
 127 
 128 
 129 # Exercise 1:
 130 # Add comments to thoroughly document this script.
 131 
 132 # Exercise 2:
 133 # Add a routine (function) to print out each hand sorted in suits.
 134 # You may add other bells and whistles if you like.
 135 
 136 # Exercise 3:
 137 # Simplify and streamline the logic of the script.