فصل ‎24‎- توابع

مانند زبان‌های برنامه‌نویسی «واقعی»‏، ‎Bash‎ هم دارای توابع است، اگرچه در یک پیاده‌سازی نسبتا محدود. تابع یک subroutine (روال فرعی) است، یک بلوک کد که مجموعه‌ای از عملیات را انجام می‌دهد، یک «جعبه سیاه» که وظیفه تعیین شده‌ای انجام می‌دهد. آنجا که کد تکراری وجود دارد، موقعی که یک وظیفه با اندک تغییری در طرز کار تکرار می‌شود، در آن هنگام استفاده از یک تابع را در نظر بگیرید.

functionfunction_name {
command...
}

یا

function_name () {
command...
}

این شکل دوم، برای برنامه‌نویسان C خوشایند خواهد بود (و بیشتر قابل حمل است).

همچون در C، کمانکِ باز کردن تابع می‌تواند در سطر دوم قرار بگیرد.

function_name ()
{
command...
}

یک تابع می‌تواند در یک سطر واحد «فشرده» بشود.

fun () { echo "This is a function"; echo; }
#                                 ^     ^

اما، در این حالت، باید یک semicolon بعد از فرمان انتهایی تابع قرار بگیرد.

fun () { echo "This is a function"; echo }    # Error!
#                                       ^

fun2 () { echo "Even a single-command function? Yes!"; }
#                                                    ^

توابع triggered (ماشه کشیده) نامیده می‌شوند، به سادگی به وسیله نامشان فراخوانی می‌گردند. یک فراخوان تابع معادل یک فرمان است.

مثال ‎24-1‎. توابع ساده

#!/bin/bash
# ex59.sh:   تمرین توابع (ساده).

JUST_A_SECOND=1

funky ()
{        #     این تقریباً ساده‌ترین حالت توابع است.
  echo "This is a funky function."
  echo "Now exiting funky function."
}        #تعریف تابع باید قبل از فراخوانی آن باشد.


fun ()
{        #           یک تابع تا اندازه‌ای پیچیده‌تر.
  i=0
  REPEATS=30

  echo
  echo "And now the fun really begins."
  echo

  sleep $JUST_A_SECOND   #هی، یک ثانیه منتظر بمان!
  while [ $i -lt $REPEATS ]
  do
    echo "----------FUNCTIONS---------->"
    echo "<------------ARE-------------"
    echo "<------------FUN------------>"
    echo
    let "i+=1"
  done
}

                          #اکنون، فراخوانی تابع‌ها.

funky
fun

exit $?

تعریف تابع باید مقدم بر اولین فراخوانی آن باشد. روشی برای «اعلان کردن» تابع، به طور مثال، همچون در C وجود ندارد.

f1
#یک پیغام خطا ارایه خواهد نمود، چون هنوز تابع ‎f1‎ تعریف نشده.

declare -f f1      #این هم کمکی نمی‌کند.
f1                 #بازهم یک پیغام خطا.

# اما...

	  
f1 ()
{
  echo "Calling function \"f2\" from within function \"f1\"."
  f2
}

f2 ()
{
  echo "Function \"f2\"."
}

f1  #تابع ‎f2‎ در حقیقت تا این نقطه فراخوانی نشده، اگرچه قبل از
    #+ تعریف شدن آن، مورد ارجاع قرار گرفته است. این مجاز است.
    
    # با تشکر از ‎S.C‎

توابع نمی‌توانند تهی باشند!
#!/bin/bash
# empty-function.sh

empty ()
{
}

exit 0  # اینجا خارج نخواهد شد!

bash$ sh empty-function.sh
 empty-function.sh: line 6: syntax error near unexpected token `}'
 empty-function.sh: line 6: `}'

bash$ echo $?
 2
#توجه نمایید که یک تابع شامل توضیحات تنها، نیز تهی است. func () { # Comment 1. # Comment 2. #این هنوز یک تابع تهی است. # تشکر از ‎Mark Bova‎ برای اشاره به این مورد. } # به همان پیغام خطای فوق منجر می‌گردد. # اما ... not_quite_empty () { illegal_command } # یک اسکریپت شامل این تابع، مادامیکه تابع، #+ فراخوانی نشود ناکام نیست. not_empty () { : } #شامل یک ‎:‎ (فرمان پوچ) است، و مورد قبول است. #با تشکر از ‎Dominick Geyer‎ و ‎Thiemo Kellner‎.

تابع حتی می‌تواند داخل یک تابع دیگر جای بگیرد، هرچند که این کار خیلی سودمند نیست.

f1 ()
{

  f2 ()   #  تو در تو
  {
    echo "Function \"f2\", inside \"f1\"."
  }

}  

f2        #  یک پیغام خطا می‌دهد. حتی اضافه کردن یک
          #«‎declare -f f2‎» قبل از آن، کمکی نمی‌کند.

echo    

f1        #کاری انجام نمی‌دهد، چون فراخوانی ‎f1‎ به طور خودکار ‎f2‎ را احضار نمی‌کند.
f2        # اکنون، فراخوانی کردن ‎f2‎ درست است، چون به وسیله فراخوانی ‎f1‎ تعریف آن
          #+                                              قابل رویت گردیده است.

#با تشکر از ‎S.C.

تعریف توابع می‌تواند در مکان‌های غیرمحتمل ظاهر گردد، حتی در جایی که بدون آن تعریف، یک فرمان عمل خواهد کرد.

ls -l | foo() { echo "foo"; }  # مجاز، اما بی‌فایده.



if [ "$USER" = bozo ]
then
  bozo_greet ()   #      تعریف تابع در یک ساختار ‎if/then‎ تعبیه گردیده است.
  {
    echo "Hello, Bozo."
  }
fi  

bozo_greet   # فقط برای ‎Bozo‎ کار می‌کند، سایر کاربران یک خطا دریافت می‌کنند.



             #         موردی مانند این، در برخی مضمون‌ها می‌تواند مفید باشد.
NO_EXIT=1    #                          تعریف تابع زیر را فعال خواهد نمود.

[[ $NO_EXIT -eq 1 ]] && exit() { true; }     # تعریف تابع در یک «‎and-list‎»
             #     اگر ‎$NO_EXIT‎ برابر ‎1‎ باشد، تابع ‎exit ()‎ را تعریف می‌کند.
             #تابع، با جانشین کردن true فرمان داخلی exit را غیرفعال می‌کند.

exit         #     تابع ‎exit ()‎ را فراخوانی می‌کند، نه فرمان داخلی exit را.



#       یا، به طور مشابه:
filename=file1

[ -f "$filename" ] &&
foo () { rm -f "$filename"; echo "File "$filename" deleted."; } ||
foo () { echo "File "$filename" not found."; touch bar; }

foo

#با تشکر از ‎S.C.‎ و ‎Christopher Head‎

توابع می‌توانند دارای نام‌هایی با شکل‌های مرموز باشند.

  _(){ for i in {1..10}; do echo -n "$FUNCNAME"; done; echo; }
# ^^^      بدون فاصله میان نام تابع و پرانتزها.
#                    این همیشه کار نمی‌کند. چرا؟

#         اکنون بیایید تابع را فراخوانی نماییم.
  _    # __________
#        ^^^^^^^^^^  ‎10‎ کاراکتر خط زیر (‎10‎ بار نام تابع)!  
#              یک خط زیر تنها، یک نام تابع قابل قبول است.

# در عمل، یک کولن نیز یک نام تابع قابل قبول است.

:(){ echo ":"; }; :

#                          مورد استفاده این چیست؟
#روشی منحرف برای مبهم نمودن کد در یک اسکریپت است.
همچنین مثال ‎A-56‎ را ببینید

وقتی نگارش‌های مختلفی از یک تابع در اسکریپت ظاهر شوند، چه اتفاق می‌افتد؟
# به طوری که ‎Yan Chen‎ اشاره می‌کند، وقتی تابعی چندبار تعریف می‌شود،
#آنچه احضار می‌شود، نگارش نهایی است. اما این استفاده بخصوصی ندارد.

func ()
{
  echo "First version of func ()."
}

func ()
{
  echo "Second version of func ()."
}

func      #  Second version of func ().‎

exit $?

# حتی می‌توان تابع را برای لغو نمودن فرمان‌های سیستم، یا تقدم
#+ یافتن بر آنها، استفاده نمود. البته، این کار عاقلانه نیست.