Chapter 35. Bash, versions 2 and 3

35.1. Bash, version2

The current version of Bash, the one you have running on your machine, is version 2.xx.y or 3.xx.y.
 bash$ echo $BASH_VERSION
 2.05.b.0(1)-release
 	      
The version 2 update of the classic Bash scripting language added array variables, [1] string and parameter expansion, and a better method of indirect variable references, among other features.


Example 35-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 echo $'Three form feeds \f \f \f'
  12 echo $'10 newlines \n\n\n\n\n\n\n\n\n\n'
  13 echo $'\102\141\163\150'   # Bash
  14                            # Octal equivalent of characters.
  15 
  16 exit 0


Example 35-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 greatly superior to the old "eval var1=\$$var2"
  14 
  15 echo
  16 
  17 t=table_cell_3
  18 table_cell_3=24
  19 echo "t = ${!t}"                      # t = 24
  20 table_cell_3=387
  21 echo "Value of t changed to ${!t}"    # 387
  22 
  23 #  This is useful for referencing members of an array or table,
  24 #+ or for simulating a multi-dimensional array.
  25 #  An indexing option would have been nice (sigh).
  26 
  27 exit 0


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

   1 #!/bin/bash
   2 # resistor-inventory.sh
   3 # Simple database application using indirect variable referencing.
   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=.25
  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   echo "There are ${!Inv} of [${!Val} ohm / ${!Pdissip} watt] resistors in stock."
  46   echo "These are located in bin # ${!Loc}."
  47   echo "Their color code is \"${!Ccode}\"."
  48 
  49   break
  50 done
  51 
  52 echo; echo
  53 
  54 # Exercises:
  55 # ---------
  56 # 1) Rewrite this script to read its data from an external file.
  57 # 2) Rewrite this script to use arrays,
  58 #+   rather than indirect variable referencing.
  59 #    Which method is more straightforward and intuitive?
  60 
  61 
  62 # Notes:
  63 # -----
  64 #  Shell scripts are inappropriate for anything except the most simple
  65 #+ database applications, and even then it involves workarounds and kludges.
  66 #  Much better is to use a language with native support for data structures,
  67 #+ such as C++ or Java (or even Perl).
  68 
  69 exit 0


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

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

Notes

[1]

Chet Ramey promises associative arrays (a Perl feature) in a future Bash release. As of version 3, this has not yet happened.