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
. به این ترتیب، اسکریپت فراخوان شده، میداند که مشکل در کجاست.