Vous êtes sur la page 1sur 15

Prev

Advanced Bash-Scripting Guide: Chapter 16. External Filters, Programs and Commands

Next

16.8. Math Commands


"Doing the numbers" factor Decompose an integer into prime factors.
bash$ factor 27417 27417: 3 13 19 37

Example 16-46. Generating prime numbers


#!/bin/bash # primes2.sh # Generating prime numbers the quick-and-easy way, #+ without resorting to fancy algorithms. CEILING=10000 PRIME=0 E_NOTPRIME= # 1 to 10000

is_prime () { local factors factors=( $(factor $1) )

# Load output of `factor` into array.

if [ -z "${factors[2]}" ] # Third element of "factors" array: #+ ${factors[2]} is 2nd factor of argument. # If it is blank, then there is no 2nd factor, #+ and the argument is therefore prime. then return $PRIME # 0 else return $E_NOTPRIME # null fi } echo for n in $(seq $CEILING) do if is_prime $n then printf %5d $n fi # ^ Five positions per number suffices.

done # necessary. echo exit

For a higher $CEILING, adjust upward, as

bc Bash can't handle floating point calculations, and it lacks operators for certain important mathematical functions. Fortunately, bc gallops to the rescue. Not just a versatile, arbitrary precision calculation utility, bc offers many of the facilities of a programming language. It has a syntax vaguely resembling C. Since it is a fairly well-behaved UNIX utility, and may therefore be used in a pipe, bc comes in handy in scripts. Here is a simple template for using bc to calculate a script variable. This uses command substitution.
variable=$(echo "OPTIONS; OPERATIONS" | bc)

Example 16-47. Monthly Payment on a Mortgage


#!/bin/bash # monthlypmt.sh: Calculates monthly payment on a mortgage. # This is a modification of code in the #+ "mcalc" (mortgage calculator) package, #+ by Jeff Schmidt #+ and #+ Mendel Cooper (yours truly, the ABS Guide author). # http://www.ibiblio.org/pub/Linux/apps/financial/mcalc1.6.tar.gz echo echo "Given the principal, interest rate, and term of a mortgage," echo "calculate the monthly payment." bottom=1.0 echo echo -n "Enter principal (no commas) " read principal echo -n "Enter interest rate (percent) " not ".12". read interest_r echo -n "Enter term (months) " read term

# If 12%, enter "12",

interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # Convert to decimal. # ^^^^^^^^^^^^^^^^^ Divide by 100. # "scale" determines how many decimal places. interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc) top=$(echo "scale=9; $principal*$interest_rate^$term" | bc) # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # Standard formula for figuring interest. echo; echo "Please be patient. This may take a while." let "months = $term - 1" # ================================================================== == for ((x=$months; x > 0; x--)) do bot=$(echo "scale=9; $interest_rate^$x" | bc) bottom=$(echo "scale=9; $bottom+$bot" | bc) # bottom = $(($bottom + $bot")) done # ================================================================== == # ------------------------------------------------------------------# Rick Boivie pointed out a more efficient implementation #+ of the above loop, which decreases computation time by 2/3. # for ((x=1; x <= $months; x++)) # do # bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc) # done # And then he came up with an even more efficient alternative, #+ one that cuts down the run time by about 95%! # bottom=`{ # echo "scale=9; bottom=$bottom; interest_rate=$interest_rate" # for ((x=1; x <= $months; x++)) # do # echo 'bottom = bottom * interest_rate + 1' # done # echo 'bottom' # } | bc` # Embeds a 'for loop' within command substitution. # -------------------------------------------------------------------------

# On the other hand, Frank Wang suggests: # bottom=$(echo "scale=9; ($interest_rate^$term-1)/ ($interest_rate-1)" | bc) # Because . . . # The algorithm behind the loop #+ is actually a sum of geometric proportion series. # The sum formula is e0(1-q^n)/(1-q), #+ where e0 is the first element and q=e(n+1)/e(n) #+ and n is the number of elements. # ------------------------------------------------------------------------# let "payment = $top/$bottom" payment=$(echo "scale=2; $top/$bottom" | bc) # Use two decimal places for dollars and cents. echo echo "monthly payment = \$$payment" front of amount. echo exit 0 # Exercises: # 1) Filter # 2) Filter or decimal. # 3) If you #+ expand # Echo a dollar sign in

input to permit commas in principal amount. input to permit interest to be entered as percent are really ambitious, this script to print complete amortization tables.

Example 16-48. Base Conversion


#!/bin/bash ################################################################## ######### # Shellscript: base.sh - print number to different bases (Bourne Shell) # Author : Heiner Steven (heiner.steven@odn.de) # Date : 07-03-95 # Category : Desktop # $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $ # ==> Above line is RCS ID info. ################################################################## ######### # Description # # Changes # 21-03-95 stv fixed error occuring with 0xb as input (0.2) ################################################################## #########

# ==> Used in ABS Guide with the script author's permission. # ==> Comments added by ABS Guide author. NOARGS=85 PN=`basename "$0"` # Program name VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2` # ==> VER=1.2 Usage () { echo "$PN - print number to different bases, $VER (stv '95) usage: $PN [number ...] If no number is given, the numbers are read from standard input. A number may be binary (base 2) starting with 0b (i.e. 0b1100) octal (base 8) starting with 0 (i.e. 014) hexadecimal (base 16) starting with 0x (i.e. 0xc) decimal otherwise (i.e. 12)" >&2 exit $NOARGS } # ==> Prints usage message. Msg () { for i # ==> in [list] missing. Why? do echo "$PN: $i" >&2 done } Fatal () { Msg "$@"; exit 66; } PrintBases () { # Determine base of the number for i # ==> in [list] missing... do # ==> so operates on command-line arg(s). case "$i" in 0b*) ibase=2;; # binary 0x*|[a-f]*|[A-F]*) ibase=16;; # hexadecimal 0*) ibase=8;; # octal [1-9]*) ibase=10;; # decimal *) Msg "illegal number $i - ignored" continue;; esac # Remove prefix, convert hex digits to uppercase (bc needs this). number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'` # ==> Uses ":" as sed separator, rather than "/". # Convert number to decimal dec=`echo "ibase=$ibase; $number" | bc` # ==> 'bc' is calculator utility. case "$dec" in [0-9]*) ;; # number ok *) continue;; # error: ignore esac

# Print all conversions in one line. # ==> 'here document' feeds command list to 'bc'. echo `bc <<! obase=16; "hex="; $dec obase=10; "dec="; $dec obase=8; "oct="; $dec obase=2; "bin="; $dec ! ` | sed -e 's: : done } while [ $# -gt 0 ] # ==> Is a "while loop" really necessary here, # ==>+ since all the cases either break out of the loop # ==>+ or terminate the script. # ==> (Above comment by Paulo Marcel Coelho Aragao.) do case "$1" in --) shift; break;; -h) Usage;; # ==> Help message. -*) Usage;; *) break;; # First number esac # ==> Error checking for illegal input might be appropriate. shift done if [ $# -gt 0 ] then PrintBases "$@" else while read line do PrintBases $line done fi exit :g'

# Read from stdin.

An alternate method of invoking bc involves using a here document embedded within a command substitution block. This is especially appropriate when a script needs to pass a list of options and commands to bc.
variable=`bc << LIMIT_STRING options statements operations LIMIT_STRING ` ...or...

variable=$(bc << LIMIT_STRING options statements operations LIMIT_STRING )

Example 16-49. Invoking bc using a here document


#!/bin/bash # Invoking 'bc' using command substitution # in combination with a 'here document'. var1=`bc << EOF 18.33 * 19.78 EOF ` echo $var1

# 362.56

# $( ... ) notation also works. v1=23.53 v2=17.881 v3=83.501 v4=171.63 var2=$(bc << EOF scale = 4 a = ( $v1 + $v2 ) b = ( $v3 * $v4 ) a * b + 15.35 EOF ) echo $var2 # 593487.8452 var3=$(bc -l << EOF scale = 9 s ( 1.7 ) EOF ) # Returns the sine of 1.7 radians. # The "-l" option calls the 'bc' math library. echo $var3 # .991664810 # Now, try it in a function... hypotenuse () # Calculate hypotenuse of a right triangle. { # c = sqrt( a^2 + b^2 ) hyp=$(bc -l << EOF scale = 9 sqrt ( $1 * $1 + $2 * $2 ) EOF )

# Can't directly return floating point values from a Bash function. # But, can echo-and-capture: echo "$hyp" } hyp=$(hypotenuse 3.68 7.31) echo "hypotenuse = $hyp" # 8.184039344 exit 0

Example 16-50. Calculating PI


#!/bin/bash # cannon.sh: Approximating PI by firing cannonballs. # Author: Mendel Cooper # License: Public Domain # Version 2.2, reldate 13oct08. # This is a very simple instance of a "Monte Carlo" simulation: #+ a mathematical model of a real-life event, #+ using pseudorandom numbers to emulate random chance. # Consider a perfectly square plot of land, 10000 units on a side. # This land has a perfectly circular lake in its center, #+ with a diameter of 10000 units. # The plot is actually mostly water, except for land in the four corners. # (Think of it as a square with an inscribed circle.) # # We will fire iron cannonballs from an old-style cannon #+ at the square. # All the shots impact somewhere on the square, #+ either in the lake or on the dry corners. # Since the lake takes up most of the area, #+ most of the shots will SPLASH! into the water. # Just a few shots will THUD! into solid ground #+ in the four corners of the square. # # If we take enough random, unaimed shots at the square, #+ Then the ratio of SPLASHES to total shots will approximate #+ the value of PI/4. # # The simplified explanation is that the cannon is actually #+ shooting only at the upper right-hand quadrant of the square, #+ i.e., Quadrant I of the Cartesian coordinate plane. # # # Theoretically, the more shots taken, the better the fit. # However, a shell script, as opposed to a compiled language #+ with floating-point math built in, requires some compromises. # This decreases the accuracy of the simulation.

DIMENSION=10000 generated. MAXSHOTS=1000 too long. PMULTIPLIER=4.0

# Length of each side of the plot. # Also sets ceiling for random integers # Fire this many shots. # 10000 or more would be better, but would take # Scaling factor.

declare -r M_PI=3.141592654 # Actual 9-place value of PI, for comparison purposes. get_random () { SEED=$(head -n 1 /dev/urandom | od -N 1 | awk '{ RANDOM=$SEED # random.sh" #+ let "rnum = $RANDOM % $DIMENSION" # 10000. echo $rnum }

print $2 }') From "seedingexample script. Range less than

distance= # Declare global variable. hypotenuse () # Calculate hypotenuse of a right triangle. { # From "alt-bc.sh" example. distance=$(bc -l << EOF scale = 0 sqrt ( $1 * $1 + $2 * $2 ) EOF ) # Setting "scale" to zero rounds down result to integer value, #+ a necessary compromise in this script. # It decreases the accuracy of this simulation. } # ========================================================== # main() { # "Main" code block, mimicking a C-language main() function. # Initialize variables. shots=0 splashes=0 thuds=0 Pi=0 error=0 while [ "$shots" -lt do xCoord=$(get_random) Y coords. yCoord=$(get_random) "$MAXSHOTS" ] # Main loop. # Get random X and

hypotenuse $xCoord $yCoord distance. ((shots++)) printf printf printf printf -(0,0). if [ "$distance" -le "$DIMENSION" ] then echo -n "SPLASH! " ((splashes++)) else echo -n "THUD! " ((thuds++)) fi "#%4d " $shots "Xc = %4d " $xCoord "Yc = %4d " $yCoord "Distance = %5d " $distance

# Hypotenuse of #+ right-triangle =

# #+ #+ #+

Distance from center of lake -- the "origin" coordinate

Pi=$(echo "scale=9; $PMULTIPLIER*$splashes/$shots" | bc) # Multiply ratio by 4.0. echo -n "PI ~ $Pi" echo done echo echo "After $shots shots, PI looks like approximately $Pi" # Tends to run a bit high, #+ possibly due to round-off error and imperfect randomness of $RANDOM. # But still usually within plus-or-minus 5% . . . #+ a pretty fair rough approximation. error=$(echo "scale=9; $Pi - $M_PI" | bc) pct_error=$(echo "scale=2; 100.0 * $error / $M_PI" | bc) echo -n "Deviation from mathematical value of PI = $error" echo " ($pct_error% error)" echo # End of "main" code block. # } # ========================================================== exit 0 # One might well wonder whether a shell script is appropriate for #+ an application as complex and computation-intensive as a simulation. # # There are at least two justifications.

# 1) As a proof of concept: to show it can be done. # 2) To prototype and test the algorithms before rewriting #+ it in a compiled high-level language.

See also Example A-37. dc The dc (desk calculator) utility is stack-oriented and uses RPN (Reverse Polish Notation). Like bc, it has much of the power of a programming language. Similar to the procedure with bc, echo a command-string to dc.
echo "[Printing a string ... ]P" | dc # The P command prints the string between the preceding brackets. # And now for some simple arithmetic. echo "7 8 * p" | dc # 56 # Pushes 7, then 8 onto the stack, #+ multiplies ("*" operator), then prints the result ("p" operator).

Most persons avoid dc, because of its non-intuitive input and rather cryptic operators. Yet, it has its uses. Example 16-51. Converting a decimal number to hexadecimal
#!/bin/bash # hexconvert.sh: Convert a decimal number to hexadecimal. E_NOARGS=85 # Command-line arg missing. BASE=16 # Hexadecimal. if [ -z "$1" ] then # Need a command-line argument. echo "Usage: $0 number" exit $E_NOARGS fi # Exercise: add argument validity checking. hexcvt () { if [ -z "$1" ] then echo 0 return # "Return" 0 if no arg passed to function. fi echo ""$1" "$BASE" o p" | dc # o sets radix (numerical base) of output. # p prints the top of stack. # For other options: 'man dc' ... return

} hexcvt "$1" exit

Studying the info page for dc is a painful path to understanding its intricacies. There seems to be a small, select group of dc wizards who delight in showing off their mastery of this powerful, but arcane utility.
bash$ echo "16i[q]sa[ln0=aln100%Pln100/snlbx]sbA0D68736142snlbxq" | dc Bash dc <<< 10k5v1+2/p # 1.6180339887 # ^^^ Feed operations to dc using a Here String. # ^^^ Pushes 10 and sets that as the precision (10k). # ^^ Pushes 5 and takes its square root # (5v, v = square root). # ^^ Pushes 1 and adds it to the running total (1+). # ^^ Pushes 2 and divides the running total by that (2/). # ^ Pops and prints the result (p) # The result is 1.6180339887 ... # ... which happens to be the Pythagorean Golden Ratio, to 10 places.

Example 16-52. Factoring


#!/bin/bash # factr.sh: Factor a number MIN=2 # Will not work for number smaller than this. E_NOARGS=85 E_TOOSMALL=86 if [ -z $1 ] then echo "Usage: $0 number" exit $E_NOARGS fi if [ "$1" -lt "$MIN" ] then echo "Number to factor must be $MIN or greater." exit $E_TOOSMALL fi # Exercise: Add type checking (to reject non-integer arg). echo "Factors of $1:" # ------------------------------------------------------echo "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=\ 1lrli2+dsi!>.]ds.xd1<2" | dc

# ------------------------------------------------------# Above code written by Michel Charpentier <charpov@cs.unh.edu> # (as a one-liner, here broken into two lines for display purposes). # Used in ABS Guide with permission (thanks!). exit # # # # # $ sh factr.sh 270138 2 3 11 4093

awk Yet another way of doing floating point math in a script is using awk's built-in math functions in a shell wrapper. Example 16-53. Calculating the hypotenuse of a triangle
#!/bin/bash # hypotenuse.sh: Returns the "hypotenuse" of a right triangle. # (square root of sum of squares of the "legs") ARGS=2 E_BADARGS=85 # Script needs sides of triangle passed. # Wrong number of arguments.

if [ $# -ne "$ARGS" ] # Test number of arguments to script. then echo "Usage: `basename $0` side_1 side_2" exit $E_BADARGS fi AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } ' # command(s) / parameters passed to awk # Now, pipe the parameters to awk. echo -n "Hypotenuse of $1 and $2 = " echo $1 $2 | awk "$AWKSCRIPT" # ^^^^^^^^^^^^ # An echo-and-pipe is an easy way of passing shell parameters to awk. exit # Exercise: Rewrite this script using 'bc' rather than awk. # Which method is more intuitive?

Shell scripting to find prime number?

!/bin/shecho "Enter the number"read numbertemp=`expr $number - 1`for i in `seq 2 $temp`do mod=`expr $number % $i` if [ $mod = 0 ] then echo "Given Number is not a prime" exit fidoneecho "Given number...

Shell script to find area of a circle? echo -n "Enter the radius of a circle : "read r # use formula to get itarea=$(echo "scale=2;3.14 * ($r * $r)" | bc) # use formula to get itd=$ (echo "scale=2;2 * $r"|bc)circumference=$(echo...

Write a Shell script to find out the user is valid or not? !/bin/sh echo "Please enter the username you want to check: " read USER grep $USER /etc/passwd > /tmp/lusertest sleep 5 if [ -s /tmp/lusertest ] then echo "User exists" else echo "User does not...

Where do you find scripts? Drew's Script-O-Rama has a wide range of screenplays and movie scripts. http://www.script-o-rama.com

Find prime prime factorization of 100? the prime factorization is 2 x 2 x 5 x 5!

Shell script to find a prime no?


In: Uncategorized [Edit categories] Answer: i=2 rem=1 echo -e "Enter a number: \c" read num if [ $num -lt 2 ]; then echo -e "$num is not prime\n" exit 0 fi while [ $i -le `expr $num / 2` -a $rem -ne 0 ]; do rem=`expr $num % $i` i=`expr $i + 1` done if [ $rem -ne 0 ]; then echo -e "$num is prime\n" else echo -e "$num is not prime\n" fi
Write a shell script to check whether given year is a leap year or not?

# store year yy=0 isleap="false" echo -n "Enter year (yyyy) : " read yy

# find out if it is a leap year or not if [ $((yy % 4)) -ne 0 ] ; then : # not a leap year : means do nothing and use old value of isleap elif [ $((yy % 400)) -eq 0 ] ; then # yes, it's a leap year isleap="true" elif [ $((yy % 100)) -eq 0 ] ; then : # not a leap year do nothing and use old value of isleap else # it is a leap year isleap="true" fi if [ "$isleap" == "true" ]; then echo "$yy is leap year" else echo "$yy is NOT leap year" fi Write a shell program to generate the first hundred numbers in the Fibonacci series?

echo "Enter How many numbers:" read 100 num1=0 num2=1 echo "Fibonacci series:" echo $num1 echo $num2 count=2 while [ $count -le $num ] do num3=`expr $num1 + $num2` echo $num3 num1=$num2 num2=$num3 count=`expr $count + 1` done

Shell Programs # Write a shell script to print the area of circleClear echo Enter the Radius of a Circle : - read radiusecho expr 22 \* $radius \* $rad / 7echo Area of Circle:echo OutputEnter the Radius of Circle: -3Area of Circle:28

Vous aimerez peut-être aussi