Chapter 34. Bash, versions 2 and 3

34.1. Bash, version 2

The current version of Bash, the one you have running on your machine, is most likely version 2.xx.yy or 3.xx.yy.
 bash$ echo $BASH_VERSION
 3.2.25(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 34-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 34-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 34-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   echo "These are located in bin # ${!Loc}."
  49   echo "Their color code is \"${!Ccode}\"."
  50 
  51   break
  52 done
  53 
  54 echo; echo
  55 
  56 # Exercises:
  57 # ---------
  58 # 1) Rewrite this script to read its data from an external file.
  59 # 2) Rewrite this script to use arrays,
  60 #+   rather than indirect variable referencing.
  61 #    Which method is more straightforward and intuitive?
  62 #    Which method is easier to code?
  63 
  64 
  65 # Notes:
  66 # -----
  67 #  Shell scripts are inappropriate for anything except the most simple
  68 #+ database applications, and even then it involves workarounds and kludges.
  69 #  Much better is to use a language with native support for data structures,
  70 #+ such as C++ or Java (or even Perl).
  71 
  72 exit 0


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

   1 #!/bin/bash
   2 
   3 # Cards:
   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"
  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 #  What are some other methods
  80 #+ of seeding the random number generator?
  81 }
  82 
  83 deal_cards ()
  84 {
  85 echo
  86 
  87 cards_picked=0
  88 while [ "$cards_picked" -le $UPPER_LIMIT ]
  89 do
  90   pick_a_card
  91   t=$?
  92 
  93   if [ "$t" -ne $DUPE_CARD ]
  94   then
  95     parse_card $t
  96 
  97     u=$cards_picked+1
  98     # Change back to 1-based indexing (temporarily). Why?
  99     let "u %= $CARDS_IN_SUIT"
 100     if [ "$u" -eq 0 ]   # Nested if/then condition test.
 101     then
 102      echo
 103      echo
 104     fi
 105     # Separate hands.
 106 
 107     let "cards_picked += 1"
 108   fi  
 109 done  
 110 
 111 echo
 112 
 113 return 0
 114 }
 115 
 116 
 117 # Structured programming:
 118 # Entire program logic modularized in functions.
 119 
 120 #===============
 121 seed_random
 122 initialize_Deck
 123 initialize_Suits
 124 initialize_Cards
 125 deal_cards
 126 #===============
 127 
 128 exit 0
 129 
 130 
 131 
 132 # Exercise 1:
 133 # Add comments to thoroughly document this script.
 134 
 135 # Exercise 2:
 136 # Add a routine (function) to print out each hand sorted in suits.
 137 # You may add other bells and whistles if you like.
 138 
 139 # Exercise 3:
 140 # Simplify and streamline the logic of the script.

Notes

[1]

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