Advanced Bash-Scripting Guide:
Prev | Chapter 37. Bash, versions 2, 3, and 4 | Next

# 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

    #!/bin/bash

    # String expansion.
    # Introduced with version 2 of Bash.

    #  Strings of the form $'xxx'
    #+ have the standard escaped characters interpreted.

    echo $'Ringing bell 3 times \a \a \a'
         # May only ring once with certain terminals.
         # Or ...
         # May not ring at all, depending on terminal settings.
    echo $'Three form feeds \f \f \f'
    echo $'10 newlines \n\n\n\n\n\n\n\n\n\n'
    echo $'\102\141\163\150'
         #   B   a   s   h
         # Octal equivalent of characters.

    exit

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

    #!/bin/bash

    # Indirect variable referencing.
    # This has a few of the attributes of references in C++.

    a=letter_of_alphabet
    letter_of_alphabet=z

    echo "a = $a"           # Direct reference.

    echo "Now a = ${!a}"    # Indirect reference.
    #  The ${!variable} notation is more intuitive than the old
    #+ eval var1=\$$var2

    echo

    t=table_cell_3
    table_cell_3=24
    echo "t = ${!t}"                      # t = 24
    table_cell_3=387
    echo "Value of t changed to ${!t}"    # 387
    # No 'eval' necessary.

    #  This is useful for referencing members of an array or table,
    #+ or for simulating a multi-dimensional array.
    #  An indexing option (analogous to pointer arithmetic)
    #+ would have been nice. Sigh.

    exit 0

    # See also, ind-ref.sh example.

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

    #!/bin/bash
    # resistor-inventory.sh
    # Simple database / table-lookup application.

    # ============================================================== #
    # Data

    B1723_value=470                                   # Ohms
    B1723_powerdissip=.25                             # Watts
    B1723_colorcode="yellow-violet-brown"             # Color bands
    B1723_loc=173                                     # Where they are
    B1723_inventory=78                                # How many

    B1724_value=1000
    B1724_powerdissip=.25
    B1724_colorcode="brown-black-red"
    B1724_loc=24N
    B1724_inventory=243

    B1725_value=10000
    B1725_powerdissip=.125
    B1725_colorcode="brown-black-orange"
    B1725_loc=24N
    B1725_inventory=89

    # ============================================================== #

    echo

    PS3='Enter catalog number: '

    echo

    select catalog_number in "B1723" "B1724" "B1725"
    do
      Inv=${catalog_number}_inventory
      Val=${catalog_number}_value
      Pdissip=${catalog_number}_powerdissip
      Loc=${catalog_number}_loc
      Ccode=${catalog_number}_colorcode

      echo
      echo "Catalog number $catalog_number:"
      # Now, retrieve value, using indirect referencing.
      echo "There are ${!Inv} of  [${!Val} ohm / ${!Pdissip} watt]\
      resistors in stock."  #        ^             ^
      # As of Bash 4.2, you can replace "ohm" with \u2126 (using echo -e).
      echo "These are located in bin # ${!Loc}."
      echo "Their color code is \"${!Ccode}\"."

      break
    done

    echo; echo

    # Exercises:
    # ---------
    # 1) Rewrite this script to read its data from an external file.
    # 2) Rewrite this script to use arrays,
    #+   rather than indirect variable referencing.
    #    Which method is more straightforward and intuitive?
    #    Which method is easier to code?

    # Notes:
    # -----
    #  Shell scripts are inappropriate for anything except the most simple
    #+ database applications, and even then it involves workarounds and kludges.
    #  Much better is to use a language with native support for data structures,
    #+ such as C++ or Java (or even Perl).

    exit 0

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

    #!/bin/bash
    # cards.sh

    # Deals four random hands from a deck of cards.

    UNPICKED=0
    PICKED=1

    DUPE_CARD=99

    LOWER_LIMIT=0
    UPPER_LIMIT=51
    CARDS_IN_SUIT=13
    CARDS=52

    declare -a Deck
    declare -a Suits
    declare -a Cards
    #  It would have been easier to implement and more intuitive
    #+ with a single, 3-dimensional array.
    #  Perhaps a future version of Bash will support multidimensional arrays.

    initialize_Deck ()
    {
    i=$LOWER_LIMIT
    until [ "$i" -gt $UPPER_LIMIT ]
    do
      Deck[i]=$UNPICKED   # Set each card of "Deck" as unpicked.
      let "i += 1"
    done
    echo
    }

    initialize_Suits ()
    {
    Suits[0]=C #Clubs
    Suits[1]=D #Diamonds
    Suits[2]=H #Hearts
    Suits[3]=S #Spades
    }

    initialize_Cards ()
    {
    Cards=(2 3 4 5 6 7 8 9 10 J Q K A)
    # Alternate method of initializing an array.
    }

    pick_a_card ()
    {
    card_number=$RANDOM
    let "card_number %= $CARDS" # Restrict range to 0 - 51, i.e., 52 cards.
    if [ "${Deck[card_number]}" -eq $UNPICKED ]
    then
      Deck[card_number]=$PICKED
      return $card_number
    else
      return $DUPE_CARD
    fi
    }

    parse_card ()
    {
    number=$1
    let "suit_number = number / CARDS_IN_SUIT"
    suit=${Suits[suit_number]}
    echo -n "$suit-"
    let "card_no = number % CARDS_IN_SUIT"
    Card=${Cards[card_no]}
    printf %-4s $Card
    # Print cards in neat columns.
    }

    seed_random ()  # Seed random number generator.
    {               # What happens if you don't do this?
    seed=`eval date +%s`
    let "seed %= 32766"
    RANDOM=$seed
    #  What are some other methods
    #+ of seeding the random number generator?
    }

    deal_cards ()
    {
    echo

    cards_picked=0
    while [ "$cards_picked" -le $UPPER_LIMIT ]
    do
      pick_a_card
      t=$?

      if [ "$t" -ne $DUPE_CARD ]
      then
        parse_card $t

        u=$cards_picked+1
        # Change back to 1-based indexing (temporarily). Why?
        let "u %= $CARDS_IN_SUIT"
        if [ "$u" -eq 0 ]   # Nested if/then condition test.
        then
         echo
         echo
        fi                  # Each hand set apart with a blank line.

        let "cards_picked += 1"
      fi
    done

    echo

    return 0
    }

    # Structured programming:
    # Entire program logic modularized in functions.

    #===============
    seed_random
    initialize_Deck
    initialize_Suits
    initialize_Cards
    deal_cards
    #===============

    exit

    # Exercise 1:
    # Add comments to thoroughly document this script.

    # Exercise 2:
    # Add a routine (function) to print out each hand sorted in suits.
    # You may add other bells and whistles if you like.

    # Exercise 3:
    # Simplify and streamline the logic of the script.

Prev | Home | Next
Bash, versions 2, 3, and 4 | Up | Bash, version 3