13. توابع

یکی از ویژگی‌های اغلب چشم‌پوشی شده برنامه نویسی اسکریپت پوسته بورن، آن است که شما به آسانی می‌توانید توابعی برای استفاده در داخل اسکریپت خود بنویسید. این کار به طور معمول به دو روش انجام می‌گردد،در یک اسکریپت ساده، تابع به راحتی در همان فایلی تعریف می‌شود ، که فراخوانی خواهد شد.
هرچند، وقتی یک مجموعه از اسکریپ‌ها نوشته می‌شود، اغلب نوشتن یک "کتابخانه" ازتوابع سودمند، و معرفی آن به عنوان منبع در ابتدای اسکریپت‌های دیگری که آن توابع را به کار می‌برند، ساده‌تر است. این مورد در آینده نشان داده خواهد شد.
هرروشی که به کار رود، شیوه نوشتن تابع یکسان است، ما اصولاً در این جا از روش اول استفاده می‌کنیم. روش دوم( کتابخانه ) اساساً به همان طریق است، غیر از دستور
. ./library.sh
که در ابتدای اسکریپ آورده می‌شود.

می‌تواند تا اندازه‌ای گیج کننده باشد، که فراخوانی تابع پوسته، از نوع روال ها یا تابع ها می‌باشد، به طور سنتی تعریف تابع آن است که یک کمیت منفرد را برمی‌گرداند،و خروجی ندارد. از طرف دیگر یک رویه مقداری را برنمی‌گرداند، اماممکن است خروجی داشته باشد. یک تابع پوسته شاید نه این باشد و نه آن، و یا هردو. به طور کلی پذیرفته شده است که در اسکریپت‌های پوسته آنها توابع نامیده شوند.

یک تابع ممکن است به چهار طریق متفاوت کمیتی را برگرداند:

  • تغییر وضعیت متغیر یا متغیرها
  • استفاده از دستور exit در انتهای اسکریپت پوسته
  • استفاده از دستور return در انتهای تابع، و بازگرداندن مقدار مورد نظر برای فراخوانی بخشی از اسکریپت پوسته
  • نمایش خروجی در خروجی استاندارد، که توسط صداکننده‌ای از قبیل ‎ c=`expr $a + $b`‎ که گیرنده است، ربوده خواهد شد

این تا اندازه‌ای مشابه C است، که در آن exit برنامه را متوقف می‌کند، و return کنترل را به صدا کننده باز می‌گرداند. تفاوت آنها این است که یک تابع پوسته نمی‌تواند پارامترها را تغییر دهد، اگر چه می‌تواند متغیرهای سراسری را تغییر دهد.

یک اسکریپت ساده با کاربرد تابع می‌تواند به این شکل باشد:


function.sh
#!/bin/sh
# A simple script with a function...

add_a_user()
{
  USER=$1
  PASSWORD=$2
  shift; shift;
  # Having shifted twice, the rest is now comments ...
  COMMENTS=$@
  echo "Adding user $USER ..."
  echo useradd -c "$COMMENTS" $USER
  echo passwd $USER $PASSWORD
  echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}

###
# Main body of script starts here
###
echo "Start of script..."
add_a_user bob letmein Bob Holness the presenter
add_a_user fred badpassword Fred Durst the singer
add_a_user bilko worsepassword Sgt. Bilko the role model
echo "End of script..."

سطر چهارم با ختم شدن به () خودش را به صورت یک تعریف تابع مشخص می‌کند. این سطر با } دنبال می‌شود، و هرچیزی در ادامه تا رسیدن به { به عنوان محتوای آن تابع در نظر گرفته می‌شود.
این کد تا زمان فراخوانی تابع غیر قابل اجرا می‌باشد. توابع در اسکریپت خوانده می‌شوند، اما به طور اساسی تا وقتی که واقعاً فراخوانی شوند، صرفنظر می‌گردند.

توجه نمایید که در این مثال دستورات useradd و passwd با دستور echo پیشوند شده‌اند - این یک تکنیک مفید اشکالزدایی برای کنترل آن است که دستورات درست، اجرا خواهند شد. این همچنین به آن معنی است که شما اسکریپت را بدون آن که کاربر ارشد( root) باشید،یا حساب‌های کاربری dodgy در سیستم خود ایجاد کنید، می‌توانید اسکریپت را اجرا کنید!

ما این ایده که اسکریپت پوسته به طور ترتیبی اجرا می‌شود را به کار برده‌ایم. این در مورد توابع صدق نمی‌کند.
در این حالت، تابع add_a_user در اسکریپت خوانده شده و ترکیب دستوری آن کنترل می‌شود، اما تا موقعی که صراحتاً فراخوانی شود، اجرا نمی‌شود.
اجرا با دستور echo عبارت‎ "Start of script..."‎ شروع می‌شود. سطر بعدی، ‎add_a_user bob letmein Bob Holness ‎  به عنوان فرخوانی تابعی به نام add_a_user تشخیص داده می‌شود، تابع احضار و با متغیرهای اضافی معینی شروع به اجرا می‌کند:

$1=bob
$2=letmein
$3=Bob
$4=Holness
$5=the
$6=presenter

بنا براین داخل این تابع متغیر ‎$1‎ برابر است با bob، صرفنظر از این که متغیر ‎$1‎ شاید در بیرون از تابع مقداردهی شده باشد.
بنابراین اگر بخواهیم در داخل تابع به ‎ $1‎ "اصلی"، رجوع کنیم، باید قبل از فراخوانی تابع یک نام به آن اختصاص بدهیم - مانند: ‎A=$1‎ ،بعد داخل تابع می‌توانیم به ‎$A‎ ارجاع بدهیم.
ما دو بار از دستور shift برای رسیدن به ‎$3‎ واز آن جا تا انتهای پارامترها در متغیر ‎$@‎ استفاده می‌کنیم. سپس تابع کاربر را اضافه میکند و کلمه عبور آن را تنظیم می‌کند. توضیحی برای این عمل echo می‌کند، و کنترل را به سطر بعد از تابع در کد اصلی باز می‌گرداند.

محدودهٔ متغیرها

برنامه نویسان سایر زبانها شاید از قواعد قلمرو توابع پوسته شگفت‌زده شوند. در اصل، دامنه‌ای غیر از پارامترهای ‎($1, $2, $@, ...)‎ وجود ندارد.
قطعه کد ساده پایین را ببینید:


#!/bin/sh

myfunc()
{
  echo "I was called as : $@"
  x=2
}

### Main script starts here 

echo "Script was called with $@"
x=1
echo "x is $x"
myfunc 1 2 3
echo "x is $x"

وقتی اسکریپت به صورت ‎scope.sh a b c‎ فراخوانی می‌شود، خروجی زیر را می‌دهد:
Script was called with a b c
x is 1
I was called as : 1 2 3
x is 2

پارامترهای ‎$@‎ در داخل تابع برای انعکاس آن که تابع چگونه فراخوانده شده، تغییرکرده‌اند. متغیر x، اگرچه، به طور مؤثری یک متغیر سراسری است - myfunc آن را تغییر داده، و آن تغییر وقتی که کنترل به اسکریپت اصلی بازگردانیده شده، هنوز مؤثر است.

اگر خروجی تابع به جای دیگری لوله‌کشی شود، تابع در یک زیر پوسته فراخوانی می‌شود - یعنی، ‎"myfunc 1 2 3 | tee out.log"‎ در نوبت دوم هنوز خواهد گفت که x برابر 1 است . به این دلیل اینطور است که در لوله (پایپ) برای تابع ‎myfunc()‎ یک پردازش پوسته جدید فراخوانی می‌شود . این می‌تواند اشکالزدایی را بسیار ناامیدکننده نماید، Astrid اسکریپتی داشت که به طورغیرمنتظره‌ای، موقعی که ‎ "| tee"‎ اضافه شده بود، خراب شد، و بی‌درنگ مشخص نبود که چرا باید چنین بشود. دستور tee باید قبل از تابع سمت چپ لوله اجرا شود ، در مثال ساده ‎ "ls | grep foo"‎، اول grep باید با stdinاش اجرا بشود، سپس وقتی که ls شروع می‌شود به stdout دستور ls ، انشعاب بزند. در اسکریپت پوسته، شل قبلاً شروع شده است، حتی پیش از آن که ما دانسته باشیم که از یک لوله tee می‌خواهیم استفاده کنیم، بنابراین سیستم عامل باید tee را شروع کند، سپس یک پوسته جدید برای فراخوانی تابع ‎myfunc()‎ شروع بشود. ناامید کننده، اما سزاوار آگاه بودن است.

توابع نمی‌توانند مقادیری که با آنها فراخوانی شده‌اند را تغییر دهند، تغییر باید از طریق تغییرات خود متغیرها انجام شود، نه پارامترهای عبور داده شده.
یک مثال این مطلب را بیشتر روشن می‌کند:


#!/bin/sh

myfunc()
{
  echo "\$1 is $1"
  echo "\$2 is $2"
  # cannot change $1 - we'd have to say:
  # 1="Goodbye Cruel"
  # which is not a valid syntax. However, we can
  # change $a:
  a="Goodbye Cruel"
}

### Main script starts here 

a=Hello
b=World
myfunc $a $b
echo "a is $a"
echo "b is $b"

این بیشتر تابع تغییرات بداندیشانه ‎ $a‎ است، چون پیغام ‎ "Hello World"‎ می‌شود‎ "خداحافظ دنیای ستمکار"‎.

بازگشت

توابع می‌توانند بازگشتی باشند - این هم مثال ساده‌ای از یک تابع فاکتوریل است:


factorial.sh
#!/bin/sh

factorial()
{
  if [ "$1" -gt "1" ]; then
    i=`expr $1 - 1`
    j=`factorial $i`
    k=`expr $1 \* $j`
    echo $k
  else
    echo 1
  fi
}


while :
do
  echo "Enter a number:"
  read x
  factorial $x
done                      

همان طور که وعده کردم، حالا در این جا استفاده از کتابخانه‌ها در اسکریپ‌های پوسته را به طور خلاصه بحث خواهیم کرد. به طوری که خواهیم دید، اینها می‌توانند برای تعریف متغیرهای عمومی نیز به کار روند.


common.lib
# common.lib
# Note no #!/bin/sh as this should not spawn 
# an extra shell. It's not the end of the world 
# to have one, but clearer not to.
#
STD_MSG="About to rename some files..."

rename()
{
  # expects to be called as: rename .txt .bak 
  FROM=$1
  TO=$2

  for i in *$FROM
  do
    j=`basename $i $FROM`
    mv $i ${j}$TO
  done
}

function2.sh
#!/bin/sh
# function2.sh
. ./common.lib
echo $STD_MSG
rename txt bak

function3.sh
#!/bin/sh
# function3.sh
. ./common.lib
echo $STD_MSG
rename html html-bak

در این جا دونمونه اسکریپت پوسته کاربر می‌بینیم، ‎ function2.sh‎ و ‎function3.sh‎ که هریک به فایل کتابخانه عمومی ‎common.lib‎، ‏source‏ شده‌اند، واز متغیرها و توابع تعریف شده در آن استفاده می‌کنند.
همچون کوه کندن نیست، فقط مثالی از چگونگی استفاده مجدد از یک کد در برنامه‌نویسی پوسته است.

کدهای خروج

برای تفصیل در باره کدهای خروج بخش کدهای خروج از قسمت نکته ها و اشاره ها در این راهنما را ملاحظه کنید. برای حالا، به‌هرحال نگاه مختصری به فراخوان return خواهیم داشت.


#!/bin/sh

adduser()
{
  USER=$1
  PASSWD=$2
  shift ; shift
  COMMENTS=$@
  useradd -c "${COMMENTS}" $USER
  if [ "$?" -ne "0" ]; then
    echo "Useradd failed"
    return 1
  fi
  passwd $USER $PASSWD
  if [ "$?" -ne "0" ]; then
    echo "Setting password failed"
    return 2
  fi
  echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}

## Main script starts here

adduser bob letmein Bob Holness from Blockbusters
if [ "$?" -eq "1" ]; then
  echo "Something went wrong with useradd"
elif [ "$?" -eq "2" ]; then 
   echo "Something went wrong with passwd"
else
  echo "Bob Holness added to the system."
fi

این اسکریپت دو فراحوان خارجی را که می‌سازد، کنترل می‌کند(useradd و passwd) را،واگر آنها شکست بخورند، به کاربر اجازه می‌دهد که بداند. سپس تابع یک کد برگشتی تعریف می‌کند، 1 برای نشان دادن هر مشکلی با فرمان useradd، و 2 برای نشان دادن هر مشکلی با passwd. به این ترتیب، اسکریپت فراخوان شده، می‌داند که مشکل در کجاست.

Steve Parker  نوشته  Bourne و Bash راهنمای آموزشی اسکریپت نویسی
لطفاً برای بهتر دیدن صفحه از فایرفاکس استفاده کنید