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

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

Prev | Home | Next
Shell Scripting Under Windows | Up | Bash, version 3