# 66 Calculating Currency Values

## #66 Calculating Currency Values

A particularly interesting use of shell scripts is to offer a command-line currency conversion routine. This proves to be a two-part task, because the latest exchange rates should be cached, but that cache needs to be refreshed every day or two so that the rates stay reasonably up-to-date for the calculations.

Hence this solution is split into two scripts. The first script gets the exchange rate from CNN's money and finance website ( http://money.cnn.com/ ) and saves it in a temporary cache file called .exchangerate . The second script provides the user interface to the exchange rate information and allows easy calculation of currency conversions.

### The Code

` #!/bin/sh # getexchrate - Scrapes the current currency exchange rates #   from CNN's money and finance website. # # Without any flags, this grabs the exchange rate values if the # current information is more than 12 hours old. It also shows # success upon completion, something to take into account if # you run this from a cron job. url="http://money.cnn.com/markets/currencies/crosscurr.html" age="+720"      # 12 hours, in minutes outf="/tmp/.exchangerate" # Do we need the new exchange rate values?  Let's check to see: # If the file is less than 12 hours old, the find fails ... if [ -f \$outf ] ; then   if [ -z "\$(find \$outf -cmin \$age -print)" ]; then     echo "  #!/bin/sh # getexchrate - Scrapes the current currency exchange rates # from CNN's money and finance website. # # Without any flags, this grabs the exchange rate values if the # current information is more than 12 hours old. It also shows # success upon completion, something to take into account if # you run this from a cron job. url="http://money.cnn.com/markets/currencies/crosscurr.html" age="+720" # 12 hours, in minutes outf="/tmp/.exchangerate" # Do we need the new exchange rate values? Let's check to see: # If the file is less than 12 hours old, the find fails ... if [ -f \$outf ] ; then if [ -z "\$(find \$outf -cmin \$age -print)" ]; then echo "\$0: exchange rate data is up-to-date." >&2 exit 1 fi fi # Actually get the latest exchange rates, translating into the # format required by the exchangerate script. lynx -dump 'http://money.cnn.com/markets/currencies/crosscurr.html'  \ grep -E '(JapanEuroCanUK)'  \ awk '{ if (NF == 5 ) { print \$1"="\$2} }'  \ tr '[:upper:]' '[:lower:]'  \ sed 's/dollar/cand/' > \$outf echo "Success. Exchange rates updated at \$(date)." exit 0  : exchange rate data is up-to-date." >&2     exit 1   fi fi # Actually get the latest exchange rates, translating into the # format required by the exchangerate script. lynx -dump 'http://money.cnn.com/markets/currencies/crosscurr.html'  \   grep -E '(JapanEuroCanUK)'  \   awk '{ if (NF == 5 ) { print "="} }'  \   tr '[:upper:]' '[:lower:]'  \   sed 's/dollar/cand/' > \$outf echo "Success. Exchange rates updated at \$(date)." exit 0 `

The other script that's important for this to work is exchangerate , the actual command users invoke to calculate currency conversions:

` #!/bin/sh # exchangerate - Given a currency amount, converts it into other major #   currencies and shows the equivalent amounts in each. # ref URL: http://money.cnn.com/markets/currencies/ showrate() {   dollars="\$(echo   cut -d. -f1)"   cents="\$(echo   cut -d. -f2  cut -c1-2)"   rate="\$dollars.\${cents:-00}" } exchratefile="/tmp/.exchangerate" scriptbc="scriptbc -p 30"  # tweak this as needed . \$exchratefile # The 0.0000000001 compensates for a rounding error bug in # many versions of bc, where 1 != 0.99999999999999  useuro="\$(\$scriptbc 1 / \$euro    + 0.000000001)"  uscand="\$(\$scriptbc 1 / \$canada  + 0.000000001)"   usyen="\$(\$scriptbc 1 / \$japan   + 0.000000001)" uspound="\$(\$scriptbc 1 / \$uk      + 0.000000001)" if [ \$# -ne 2 ] ; then   echo "Usage: \$(basename  #!/bin/sh # exchangerate - Given a currency amount, converts it into other major # currencies and shows the equivalent amounts in each. # ref URL: http://money.cnn.com/markets/currencies/ showrate() { dollars="\$(echo \$1  cut -d. -f1)" cents="\$(echo \$1  cut -d. -f2  cut -c1-2)" rate="\$dollars.\${ cents :-00}" } exchratefile="/tmp/.exchangerate" scriptbc="scriptbc -p 30" # tweak this as needed . \$exchratefile # The 0.0000000001 compensates for a rounding error bug in # many versions of bc, where 1 != 0.99999999999999 useuro="\$(\$scriptbc 1 / \$euro + 0.000000001)" uscand="\$(\$scriptbc 1 / \$canada + 0.000000001)" usyen="\$(\$scriptbc 1 / \$japan + 0.000000001)" uspound="\$(\$scriptbc 1 / \$uk + 0.000000001)" if [ \$# -ne 2 ] ; then echo "Usage: \$(basename \$0) amount currency" echo "Where currency can be USD, Euro, Canadian, Yen, or Pound." exit 0 fi amount=\$1 currency="\$(echo \$2  tr '[:upper:]' '[:lower:]'  cut -c1-2)" case \$currency in usdo ) if [ -z "\$(echo \$1  grep '\.')" ] ; then masterrate="\$1.00" else masterrate="\$1" fi ;; eu ) masterrate="\$(\$scriptbc \$1 \* \$euro)" ;; cacd ) masterrate="\$(\$scriptbc \$1 \* \$canada)" ;; ye ) masterrate="\$(\$scriptbc \$1 \* \$japan)" ;; post ) masterrate="\$(\$scriptbc \$1 \* \$uk)" ;; * ) echo "\$0: unknown currency specified." echo "I only know USD, EURO, CAND/CDN, YEN and GBP/ POUND ." exit 1 esac echo "Currency Exchange Rate Equivalents for \$1 \${2}:" showrate \$masterrate echo " US Dollars: \$rate" showrate \$(\$scriptbc \$masterrate \* \$useuro) echo " EC Euros: \$rate" showrate \$(\$scriptbc \$masterrate \* \$uscand) echo "Canadian Dollars: \$rate" showrate \$(\$scriptbc \$masterrate \* \$usyen) echo " Japanese Yen: \$rate" showrate \$(\$scriptbc \$masterrate \* \$uspound) echo " British Pounds : \$rate" exit 0  ) amount currency"   echo "Where currency can be USD, Euro, Canadian, Yen, or Pound."   exit 0 fi amount= currency="\$(echo   tr '[:upper:]' '[:lower:]'  cut -c1-2)" case \$currency in   usdo ) if [ -z "\$(echo   grep '\.')" ] ; then             masterrate=".00"           else             masterrate=""           fi                                             ;;   eu    ) masterrate="\$(\$scriptbc  \* \$euro)"          ;;   cacd ) masterrate="\$(\$scriptbc  \* \$canada)"        ;;   ye    ) masterrate="\$(\$scriptbc  \* \$japan)"         ;;   post ) masterrate="\$(\$scriptbc  \* \$uk)"            ;;       * ) echo "  #!/bin/sh # exchangerate - Given a currency amount, converts it into other major # currencies and shows the equivalent amounts in each. # ref URL: http://money.cnn.com/markets/currencies/ showrate() { dollars="\$(echo \$1  cut -d. -f1)" cents="\$(echo \$1  cut -d. -f2  cut -c1-2)" rate="\$dollars.\${ cents :-00}" } exchratefile="/tmp/.exchangerate" scriptbc="scriptbc -p 30" # tweak this as needed . \$exchratefile # The 0.0000000001 compensates for a rounding error bug in # many versions of bc, where 1 != 0.99999999999999 useuro="\$(\$scriptbc 1 / \$euro + 0.000000001)" uscand="\$(\$scriptbc 1 / \$canada + 0.000000001)" usyen="\$(\$scriptbc 1 / \$japan + 0.000000001)" uspound="\$(\$scriptbc 1 / \$uk + 0.000000001)" if [ \$# -ne 2 ] ; then echo "Usage: \$(basename \$0) amount currency" echo "Where currency can be USD, Euro, Canadian, Yen, or Pound." exit 0 fi amount=\$1 currency="\$(echo \$2  tr '[:upper:]' '[:lower:]'  cut -c1-2)" case \$currency in usdo ) if [ -z "\$(echo \$1  grep '\.')" ] ; then masterrate="\$1.00" else masterrate="\$1" fi ;; eu ) masterrate="\$(\$scriptbc \$1 \* \$euro)" ;; cacd ) masterrate="\$(\$scriptbc \$1 \* \$canada)" ;; ye ) masterrate="\$(\$scriptbc \$1 \* \$japan)" ;; post ) masterrate="\$(\$scriptbc \$1 \* \$uk)" ;; * ) echo "\$0: unknown currency specified." echo "I only know USD, EURO, CAND/CDN, YEN and GBP/ POUND ." exit 1 esac echo "Currency Exchange Rate Equivalents for \$1 \${2}:" showrate \$masterrate echo " US Dollars: \$rate" showrate \$(\$scriptbc \$masterrate \* \$useuro) echo " EC Euros: \$rate" showrate \$(\$scriptbc \$masterrate \* \$uscand) echo "Canadian Dollars: \$rate" showrate \$(\$scriptbc \$masterrate \* \$usyen) echo " Japanese Yen: \$rate" showrate \$(\$scriptbc \$masterrate \* \$uspound) echo " British Pounds : \$rate" exit 0  : unknown currency specified."           echo "I only know USD, EURO, CAND/CDN, YEN and GBP/POUND."          exit 1 esac echo "Currency Exchange Rate Equivalents for  :" showrate \$masterrate echo "      US Dollars: \$rate" showrate \$(\$scriptbc \$masterrate \* \$useuro) echo "        EC Euros: \$rate" showrate \$(\$scriptbc \$masterrate \* \$uscand) echo "Canadian Dollars: \$rate" showrate \$(\$scriptbc \$masterrate \* \$usyen) echo "    Japanese Yen: \$rate" showrate \$(\$scriptbc \$masterrate \* \$uspound) echo "   British Pounds: \$rate" exit 0 `

### How It Works

When run, if the exchange rate database .exchangerate is more than 12 hours out-of-date, the first script, getexchrate , grabs the latest exchange rate information from the CNN site, extracts the exchange rates for the major currencies specified in the script, and then saves them in a currency = value format. Here's how the . exchangerate data file appears after the script is run:

` \$  cat /tmp/.exchangerate  canada=0.747100 euro=1.173300 japan=0.009163 uk=1.664400 `

The second script, exchangerate , is rather long and relies on Script #9, scriptbc , for all of the mathematics involved. The basic algorithm of the script is to normalize the currency value specified in the command arguments to U.S. dollars by multiplying the specified value by the appropriate exchange rate, and then to use the relationship between the U.S. dollar and each foreign currency to calculate the equivalent value in each currency.

From a scripting point of view, note particularly how exchangerate incorporates the exchange rate values from the .exchangerate data file:

` . \$exchratefile `

This is known as sourcing a file, and it causes the specified file (script) to be read as if its contents were part of this script. This will make more sense if we contrast it with the result of the following line:

` sh \$exchratefile `

This does exactly the wrong thing: It spawns a subshell, sets the exchange rate variables within that subshell, and then quits the subshell, leaving the calling script without access to the values for these variables .

### Running the Script

This pair of scripts is typical of sophisticated Unix interaction, with getexchrate being the one "admin" script doing the necessary back-end work to ensure that the exchange rate data is correct and up-to-date, and exchangerate being the "user" script that has all the proverbial bells and whistles but doesn't touch the Internet at all.

Although the getexchrate script can be run as frequently as desired, it actually gets and updates the currency exchange rates only if \$exchratefile is more than 12 hours old. This lends itself to being a daily cron job, perhaps just during week-days (the currency markets aren't open on weekends, so the rates don't fluctuate from Friday evening to Monday morning).

The exchangerate script expects two arguments: a currency amount and a currency name. It's flexible in this regard, so 100 CDN and 100 Canadian are the same, while 25 EU and 25 Euros will also both work. If no currency name is specified, the default is USD, U.S. dollars.

### The Results

` \$  getexchrate  Success. Exchange rates updated at Thu Oct  9 23:07:27 MDT 2003. \$  exchangerate 250 yen  Currency Exchange Rate Equivalents for 250 yen:       US Dollars: 2.29         EC Euros: 1.95 Canadian Dollars: 3.06     Japanese Yen: 250.00    British Pounds: 1.37 \$  exchangerate 250 pounds  Currency Exchange Rate Equivalents for 250 pounds:       US Dollars: 416.05         EC Euros: 354.44 Canadian Dollars: 556.96     Japanese Yen: 45395.52    British Pounds: 250.00 \$  exchangerate 250 dollars  Currency Exchange Rate Equivalents for 250 dollars:       US Dollars: 250.00         EC Euros: 212.98 Canadian Dollars: 334.67     Japanese Yen: 27277.68    British Pounds: 150.22 `

### Hacking the Script

Within a network, a single system could poll the CNN site for up-to-date exchange values and push the \$exchratefile out to workstations on the system (perhaps with an ftpsyncdown script like that shown in Script #81). The exchangerate script is then all that's installed on individual systems to enable this useful functionality.

You could cobble together a web-based interface to the exchange rate script by having a page that has a text input field for the desired amount and a pop-up menu of currency types. Submit it, turn those two data snippets into the appropriate input format for the exchangerate script, and then feed the output back to the web browser with the appropriate HTML wrapper.

Wicked Cool Shell Scripts
ISBN: 1593270127
EAN: 2147483647
Year: 2004
Pages: 150
Authors: Dave Taylor

Similar book on Amazon