فرمان‌های حساب

فصل 16- فرمان‌ها، برنامه‌ها و فیلترهای خارجی

‎16.8‎- فرمان‌های حساب

کار کردن با اعداد

factor

یک عدد صحیح را به عوامل ضرب اول تجزیه می‌کند.

bash$ factor 27417

27417: 3 13 19 37

مثال ‎16-46‎. تولید اعداد اول

#!/bin/bash
#

#  روش سریع و آسان تولید اعداد اول، بدون توسل
#+                        به الگوریتم‌های خاص.

CEILING=10000                #    
PRIME=0
E_NOTPRIME=

is_prime ()
{
  local factors
  factors=( $(factor $1) )  # تخصیص خروجی ‎factor‎ به آرایه.

if [ -z "${factors[2]}" ]
#  ‎${factors[2]}‎ سومین عضو آرایه ‎factors است و
#+دومین عامل ضرب شناسه است. اگر این عضو آرایه
#  خالی باشد، یعنی دومین فاکتور وجود ندارد، و
#+              در اینصورت شناسه، عدد اول است.
then
  return $PRIME             #
else
  return $E_NOTPRIME        #
fi
}

echo
for n in $(seq $CEILING)
do
  if is_prime $n
  then
    printf %5d $n
  fi   #
done   #برای ‎$CEILING‎ بالاتر، به عدد بزرگتر مناسبی تنظیم کنید.

echo

exit

bc

‎Bash‎ نمی‌تواند محاسبات ممیز شناور را به کار ببرد، و فاقد عملگرهایی برای توابع مهم ریاضیات است. خوشبختانه، bc برای نجات‌بخشی شتاب می‌کند.

bc نه فقط یک برنامه سودمند فراگیرنده محاسبات با دقت اختیاری است، بلکه بسیاری از امکانات یک زبان برنامه‌نویسی را ارایه می‌کند. این برنامه دارای ترکیب دستوری تا حدودی شبیه C است.

چون به طور مشخص یک برنامه خوش رفتار یونیکس است، و بنابراین می‌تواند در یک لوله به کار برود، bc در اسکریپت‌ها سودمند واقع می‌شود.

این هم یک الگوی ساده برای استفاده از bc جهت محاسبه یک متغیر اسکریپت. این مثال از جایگزینی فرمان استفاده می‌کند.

variable=$(echo "OPTIONS; OPERATIONS" | bc)

مثال ‎16-47. قسط ماهانه یک وام

#!/bin/bash
#


# این یک بهبود کد بسته ‎mcalc‎ ‎(mortgage calculator)‎ نوشته ‎Jeff Schmidt‎
#+       و ‎Mendel Cooper‎ (ارادتمند شما، نگارنده این راهنمای ‎ABS‎) است.
#

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) "  #اگر ‎12%‎ است، ‎12‎ وارد کنید نه ‎.12‎
read interest_r
echo -n "Enter term (months) "
read term


 interest_r=$(echo "scale=9; $interest_r/100.0" | bc)    # 
                 #
                 #                   ‎scale‎ تعداد ارقام اعشار را تعیین می‌کند.

 interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)
 

 top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)
          #
          #            فرمول استاندارد برای محاسبه سود.

 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)
#
 done
#

#
#    ‎Rick Boivie‎ یک پیاده‌سازی کارآمدترِ حلقه فوق را
#+توضیح داده که مدت محاسبه را به دوسوم کاهش می‌دهد.

#
#
#
#


#  و سپس یک جایگزین بازهم کارآمدتر مطرح نمود، روشی
#+       که زمان اجرا را نزدیک به ‎95%‎ تقلیل می‌دهد!

#
#
#
#
#
#
#
# #یک حلقه ‎for‎ داخل جایگزینی فرمان تعبیه می‌کند.
#
#          از طرف دیگر، ‎Frank Wang‎ مورد زیر را پیشنهاد می‌کند:
#

#                                         به علت اینکه ‎. . .‎
# الگوریتم پشت حلقه در حقیقت جمع جمله‌های یک تصاعد هندسی است.
#فرمول مجموع جمله‌ها ‎e0(1-q^n)/(1-q)‎ است که در آن ‎e0‎ جمله اول
#+                 است و ‎q=e(n+1)/e(n)‎ و ‎n‎ تعداد جمله‌ها است.
#


 #
 payment=$(echo "scale=2; $top/$bottom" | bc)
 #استفاده از دو رقم اعشار برای دلارها و سنت‌ها.
 
 echo
 echo "monthly payment = \$$payment"  #نمایش یک علامت دلار جلوی مبلغ.
 echo


 exit 0


 #                                تمرین‌ها:
 #           
 #  ‎(1‎ برای اینکه کاراکترهای کاما در مقدار اصلی مجاز شود ورودی رافیلتر کنید.
 # ‎(2‎ ورودی رافیلتر کنید تا وارد نمودن نرخ به صورت درصد یا اعشاری میسر گردد.
 #   ‎(3‎ اگر واقعاً مشتاق هستید، اسکریپت را جهت چاپ جدول کامل استهلاک بسط دهید.
 

مثال ‎16-48‎. تبدیل مبنا

#!/bin/bash
#
#
#
#
#
#
#
#
# 
#
# 
#
#

#    ==> با اجازه نویسنده اسکریپت در راهنمای ‎ABS‎ استفاده گردیده است.
#     ==> توضیحاتی که به وسیله نگارنده راهنمای ‎ABS‎ اضافه گردیده است.

NOARGS=85
PN=`basename "$0"`			          #       نام برنامه
VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2`     #    ==> نگارش ‎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 
}           #  ==> پیغام نحوه استفاده را چاپ می‌کند.

Msg () {
    for i   #          ==> ‎in [list]‎ غایب است. چرا؟
    do echo "$PN: $i" >&2
    done
}

Fatal () { Msg "$@"; exit 66; }

PrintBases () {
    # Determine base of the number
    for i      #                      ==> ‎in [list]‎ غایب است...
    do         #   ==> بنابراین روی شناسه‌های خط‌فرمان عمل می‌کند.
	case "$i" in
	    0b*)		ibase=2;;	#    
	    0x*|[a-f]*|[A-F]*)	ibase=16;;	#
	    0*)			ibase=8;;	#   
	    [1-9]*)		ibase=10;;	#    
	    *)
		Msg "illegal number $i - ignored"
		continue;;
	esac

	#حذف پیشوند، تبدیل ارقام هگز به حرف بزرگ (‎bc‎ این را لازم دارد).
	number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'`
	#        ==> به جای ‏/‎ از ‎:‎ به عنوان جداکننده ‎sed‎ استفاده می‌کند.

	#                       تبدیل عدد به مبنای ده
	dec=`echo "ibase=$ibase; $number" | bc`  
	#                     ==> ‎bc‎ یک برنامه سودمند محاسبه کننده است.
	case "$dec" in
	    [0-9]*) ;;       #   عدد مورد قبول است
	    *) continue;;    # در صورت خطا: صرفنظر
	esac

	#                       چاپ تمام تبدیل‌ها در یک سطر.
	#      ==> ‎here document‎ فرمان را به ‎bc‎ تغذیه می‌کند.
	echo `bc <<!
	    obase=16; "hex="; $dec
	    obase=10; "dec="; $dec
	    obase=8;  "oct="; $dec
	    obase=2;  "bin="; $dec
!
    ` | sed -e 's: :	:g'

    done
}

while [ $# -gt 0 ]
#    ==>آیا واقعاً یک «حلقه while» در اینجا لازم است، چون تمام موارد
#          ==>+ یا از حلقه خارج می‌شوند یا اسکریپت را خاتمه می‌دهند.
#               ==> (توضیح فوق از ‎Paulo Marcel Coelho Aragao‎ است.)
do
    case "$1" in
	--)     shift; break;;
	-h)     Usage;;              #          ==>پیغام راهنمایی.
	-*)     Usage;;
         *)     break;;              #                    عدد نخست
    esac   # ==> کنترل خطا جهت ورودی غیر مجاز، می‌تواند مناسب باشد.
    shift
done

if [ $# -gt 0 ]
then
    PrintBases "$@"
else				     # خواندن از ورودی استاندارد.
    while read line
    do
	PrintBases $line
    done
fi


exit

یک روش جایگزین برای فراخوانی bc، مستلزم استفاده از here document جاسازی شده در یک بلوک جایگزینی فرمان است. این روش، بویژه موقعی مناسب است که یک اسکریپت به عبور دادن لیستی از گزینه‌ها و فرمان‌ها به bc نیاز دارد.

variable=`bc << LIMIT_STRING
options
statements
operations
LIMIT_STRING
`

...or...


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

مثال ‎16-49‎. فراخوانی bc با استفاده از here document

#!/bin/bash
#فراخوانی ‎bc‎ با استفاده از جایگزینی فرمان در تلفیق با یک ‎here document‎


var1=`bc << EOF
18.33 * 19.78
EOF
`
echo $var1       #


# نشانه‌گذاری ‎$( ... )‎ نیز کار می‌کند.
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       #


var3=$(bc -l << EOF
scale = 9
s ( 1.7 )
EOF
)
#            سینوس ‎1.7‎ رادیان را برگشت می‌دهد.
# گزینه ‎-l‎ کتابخانه ریاضی ‎bc‎ را احضار می‌کند.
echo $var3       #


#     اکنون، آن را در یک تابع امتحان کنید...
hypotenuse ()    # محاسبه وتر مثلث راست گوشه.
{                #
hyp=$(bc -l << EOF
scale = 9
sqrt ( $1 * $1 + $2 * $2 )
EOF
)
# نمی‌توان مقادیر ممیز شناور را به طور مستقیم از یک
#  تابع Bash برگشت داد، اما می‌توان echo و ضبط نمود:
echo "$hyp"
}

hyp=$(hypotenuse 3.68 7.31)
echo "hypotenuse = $hyp"    #


exit 0

مثال ‎16-50‎. محاسبه عدد پی

#!/bin/bash
#

#                          مولف: ‎Mendel Cooper‎
#                     اجازه‌نامه: ‎Public Domain‎
#             نگارش ‎2.2‎، تاریخ انتشار  ‎13oct08‎

#  این یک نمونه بسیار ساده از شبیه‌سازی «‎Monte Carlo‎» است:
#+  یک مدل ریاضی از یک رویداد واقعی، با استفاده از اعداد
#+               شبه‌تصادفی برای تقلید کردن احتمال تصادف.

#   یک قواره زمین خشک کاملاً چهارگوش ، به ضلع ‎10000‎ واحد را در نظر بگیرید.
#   این زمین دارای یک استخر کاملاً دایره‌ای در مرکزش با قطر ‎10000‎ واحد است.
#              در حقیقت غیر از خشکی در چهار گوشه، این زمین بیشتر آب است.
#      (آن را به صورت یک چهارگوشه با دایره‌ای محاط شده در آن تصور نمایید.)
#
#  ما گلوله‌های آهنی را از یک توپ مدل قدیمی به طرف مربع شلیک خواهیم نمود.
#تمام گلوله‌ها به جایی در مربع اصابت می‌کنند، در استخر، یا در گوشه‌های خشک.
# چون بیشترین سطح را استخر گرفته است، اکثر گلوله‌ها (‎SPLASH‎) در آب خواهند
#افتاد، فقط اندکی از گلوله‌ها با صدای خفه (‎THUD‎) در زمین سفت در چهار گوشه
#+                                                 مربع فرود خواهند آمد.
#
#اگر ما شلیک‌های بدون هدف و به اندازه کافی تصادفی به طرف مربع داشته‌باشیم،
#+        آنوقت نسبت ‎SPLASH‎ها به کل پرتاب‌ها تقریباً مقدار ‎PI/4‎ خواهد بود.
#          مترجم:  پرتاب‌هایی قابل قبول هستند و شمارش می‌شوند که در محدوده مربع فرود آمده باشند.
# تعریف ساده شده، آن است که توپ در عمل فقط به طرف یک چهارم سمت راست بالای
#+              چهارگوشه، یعنی ربع اول سطح در مختصات دکارتی، شلیک می‌کند.
      تصویر توسط مترجم از ویکی‌پدیا اضافه گردیده است #از لحاظ نظری، پرتاب‌های مورد قبول بیشتر، اندازه‌ مناسب‌تر. اما، یک اسکریپت #پوسته، برخلاف یک زبان compile شوندهِ دارای حساب ممیز شناور درونی، نیازمند #+ یک مقدار کوتاه آمدن است، که این کار، درجه دقت شبیه‌سازی را کاهش می‌دهد. DIMENSION=10000 # طول هر ضلع زمین. # حد اعداد صحیح تولید شده را نیز تعیین می‌کند. MAXSHOTS=1000 # شلیک گلوله‌های زیاد به این تعداد. # ‎10000‎ یا بیشتر می‌تواند بهتر باشد، اماخیلی طول می‌کشد. PMULTIPLIER=4.0 # Scaling factor. declare -r M_PI=3.141592654 #مقدار واقعی ‎PI‎ با ۹ رقم اعشار، به منظور انجام مقایسه. get_random () { SEED=$(head -n 1 /dev/urandom | od -N 1 | awk '{ print $2 }') RANDOM=$SEED #از اسکریپت مثال ‎seeding-random.sh let "rnum = $RANDOM % $DIMENSION" # محدوده کمتر از ‎10000‎. echo $rnum } distance= # تعریف متغیر سراسری. hypotenuse () # محاسبه وتر مثلث راست گوشه. { # از مثال «‎alt-bc.sh‎». distance=$(bc -l << EOF scale = 0 sqrt ( $1 * $1 + $2 * $2 ) EOF ) # تنظیم scale به صفر، نتیجه را به عدد صحیح پایین گرد می‌کند، #+ که در این اسکریپت، یک کوتاه آمدن ضروری است. # این کار، دقت و صحت این شبیه‌سازی را کاهش می‌دهد. } # # # بلوک کد ‎"Main"‎، تقلید کردن از تابع ‎main()‎ زبان C است. # ارزش‌گذاری اولیه متغیرها. shots=0 splashes=0 thuds=0 Pi=0 error=0 while [ "$shots" -lt "$MAXSHOTS" ] # حلقه اصلی. do xCoord=$(get_random) # گرفتن X و Y تصادفی. yCoord=$(get_random) hypotenuse $xCoord $yCoord #‎distance‎ = وتر مثلث‎. ((shots++)) printf "#%4d " $shots printf "Xc = %4d " $xCoord printf "Yc = %4d " $yCoord printf "Distance = %5d " $distance # فاصله از مرکز استخر #+مبداء مختصات ‎(0,0)‎. if [ "$distance" -le "$DIMENSION" ] then echo -n "SPLASH! " ((splashes++)) else echo -n "THUD! " ((thuds++)) fi Pi=$(echo "scale=9; $PMULTIPLIER*$splashes/$shots" | bc) # ضرب کردن در ‎4.0‎ echo -n "PI ~ $Pi" echo done echo echo "After $shots shots, PI looks like approximately $Pi" #به دلیل خطای گِرد کردن و غیرایده‌آل بودن تصادف در ‎$RANDOM‎، #+ احنمالاً مقداری به سمت بالا میل می‌کند. # اما باز هم معمولاً در حدود به اضافه یا منهای ‎5% . . . #+ یک تقریب غیر دقیق نسبتاً خوب. 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 # انتهای قطعه کد main # # exit 0 # شاید کسی تعجب کند که آیا یک اسکریپت پوسته برای یک برنامه‌کاربردی #+ پیچیده و دارای محاسبه‌های فراوان همچون یک شبیه‌سازی، مناسب است. # # برای این کار حداقل دو توجیه وجود دارد. # ‎(1‎ به عنوان یک دلیل اجرایی: جهت نمایش آن که می‌تواند انجام بشود. # ‎(2‎ برای نمونه اولیه و بررسی الگوریتم‌ها، قبل از بازنویسی آن در یک #+ زبان سطح بالای ترجمه شونده.

همچنین مثال ‎A-37‎ را مشاهده نمایید.

dc

برنامه سودمند dc ‎(desk calculator)‎ پشته گرا است و ‎RPN‎ یا (Reverse Polish Notation) یعنی نشانه‌گذاری لهستانی معکوس ‎[1]‎ را به کار می‌برد. مانند bc، مقدار زیادی از قدرت یک زبان برنامه‌نویسی را در خود دارد.

مشابه شیوه کار با bc، به dc یک رشته فرمان را echo کنید.

echo "[Printing a string ... ]P" | dc
#  فرمان P رشته میان براکت‌های مقدم را چاپ می‌کند.

#                و اکنون برای برخی محاسبات ساده.
echo "7 8 * p" | dc     # 
#‎7‎ و ‎8‎ را در پشته جای می‌دهد، ضرب می‌کند (عملگر *)‏
#+             سپس نتیجه را چاپ (عملگر p) می‌کند.

بیشتر افراد به علت ورودی غیر شهودی و عملگرهای نسبتاً مبهم dc از آن پرهیز می‌کنند. با این حال موارد استفاده‌ خودش را دارد.

مثال ‎16-51‎. تبدیل یک عدد دسیمال به هگزادسیمال

#!/bin/bash
#

E_NOARGS=85    #               
BASE=16        #                      

if [ -z "$1" ]
then           #         
  echo "Usage: $0 number"
  exit $E_NOARGS
fi             #         تمرین: کنترل صحت شناسه را اضافه کنید.


hexcvt ()
{
if [ -z "$1" ]
then
  echo 0
  return       #   برگشت دادن ‎0‎ اگر شناسه به تابع رد نشده باشد.
fi

echo ""$1" "$BASE" o p" | dc
#  o مبنای (پایه عددی) خروجی را تنظیم می‌کند
#                p بالای پشته را چاپ می‌کند
#                               برای سایر گزینه‌ها: «‎man dc‎» ...
return
}

hexcvt "$1"

exit

مطالعه صفحه info برای dc یک روش آزاردهنده برای فهمیدن ریزه‌کاری‌های آن است. به نظر می‌رسد گروه کوچک خاصی از نابغه‌های dc باشند که از نمایاندن تسلط خود بر این برنامه نیرومند اما محرمانه لذت ببرند.

bash$ echo "16i[q]sa[ln0=aln100%Pln100/snlbx]sbA0D68736142snlbxq" | dc
Bash

dc <<< 10k5v1+2/p #
#              تغذیه عملگرها به ‎dc‎ با استفاده از یک ‎Here String‎
#        جای دادن ‎10‎ و قرار دادن آن به عنوان دقت اعشار‎(10k)‎‎
#      جای دادن ‎5‎ و جذر گرفتن از آن (‎5v‎‏، ‎v‎ یعنی ریشه دوم)
#      جای دادن ‎1‎ و افزودن آن به نتیجه به دست آمده ‎(1+)‎
#       جای دادن ‎2‎ و تقسیم کردن نتیجه حاصل بر آن ‎(2/)‎
#       برداشتن نتیجه از بالای پشته و چاپ کردن آن ‎(p)‎
#      نتیجه برابر با ‎1.6180339887‎ است که اتفاقاً نسبت طلایی فیثاغورثی 
#                                                با ده رقم اعشار است.


مثال ‎16-52‎. فاکتورگیری

#!/bin/bash
#           

MIN=2       #برای اعداد کوچکتر از این کار نمی‌کند.
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  

# تمرین: کنترل نوع را (برای عدم پذیرش شناسه غیر عدد صحیح) اضافه کنید.

echo "Factors of $1:"
#
echo  "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=\
1lrli2+dsi!>.]ds.xd1<2" | dc
#
#   کد فوق توسط ‎Michel Charpentier <charpov@cs.unh.edu>‎ نوشته شده است
#(به عنوان کد یک سطری که در اینجا برای نمایش به دو سطر شکسته شده است.
#                   در راهنمای ‎ABS‎ با مجوز استفاده گردیده است (تشکر!).

 exit
bash$ factr.sh 270138

  2
  3
  11
  4093

awk

اما یک روش دیگر انجام حساب ممیز شناور در یک اسکریپت، استفاده از توابع حساب توکار awk در یک shell wrapper است.

مثال ‎16-53‎. محاسبه وتر مثلث راست گوشه

#!/bin/bash
#
#              

ARGS=2                 #
E_BADARGS=85           #               

if [ $# -ne "$ARGS" ]  #       
then
  echo "Usage: `basename $0` side_1 side_2"
  exit $E_BADARGS
fi


AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } '
#             فرمان(ها)‏-پارامترهای عبور داده شده به ‎awk‎


#            اکنون، لوله‌کشی پارامترها به ‎awk‎
    echo -n "Hypotenuse of $1 and $2 = "
    echo $1 $2 | awk "$AWKSCRIPT"
#
#  یک ‎echo-and-pipe‎ روش آسانی برای تحویل دادن پارامترها به ‎awk‎ است.

exit

#تمرین: این اسکریپت را با استفاده از ‎bc‎ به جای ‎awk‎ بازنویسی نمایید.
#                                      کدام روش بیشتر قابل فهم است؟

یادداشت‌ها

[1]

مترجم: نشانه گذاری لهستانی معکوس (Reverse Polish Notation) که (postfix notation) یا نشانه‌گذاری پسوندی نیز نامیده می‌شود روشی برای نوشتن عبارت‌های ریاضی است.
در این روش از پرانتز استفاده نمی‌شود و هر عملگر به دنبال عملوندهایش می‌آید.
شاید ذکر یک مثال خالی از فایده نباشد.

#   نشانه گذاری معمولی
((4 + 16) - (3 * 5)) % 2 = ?
(  (20)   -   (15) ) % 2 = ?
        ( 5 )        % 2 = 1

#  نشانه گذاری پسوندی
4 16 + 3  5 * - 2 % = ?
______ ______ 
   20    15   - 2 % = ?
   ____________ 
       5        2 % =1