فصل 24- توابع

‎24.2‎- متغیرهای محلی

چه چیز یک متغیر را محلی می‌سازد؟

متغیرهای محلی

متغیر تعریف شده به صورت محلی، متغیری است که فقط در درون بلوک کدی که در آن ظاهر می‌شود قابل دیدن است. این متغیر دارای حوزه محلی است. در یک تابع، متغیر محلی فقط در درون بلوک آن تابع معنا دارد. [1]

مثال ‎24-12‎. پیدایی متغیر محلی

#!/bin/bash
# ex62.sh: متغیرهای محلی و سراسری داخل یک تابع.

func ()
{
  local loc_var=23       #       به عنوان متغیر محلی تعریف شده.
  echo                   #  از فرمان داخلی ‎local‎ استفاده می‌شود.
  echo "\"loc_var\" in function = $loc_var"
  global_var=999         #     به عنوان محلی تعریف نگردیده است.
                         # بنابراین به طور پیش‌فرض سراسری می‌شود. 
  echo "\"global_var\" in function = $global_var"
}  

func

#اکنون، دیدن آنکه آیا متغیر محلی ‎loc_var‎ خارج از تابع وجود دارد.

echo
echo "\"loc_var\" outside function = $loc_var"
                            #      "loc_var" outside function = 
#                     خیر، ‎loc_var‎ به طور سراسری قابل دیدن نیست.
echo "\"global_var\" outside function = $global_var"
                            #"global_var" outside function = 999
#                        ‎global_var‎ به طور سراسری قابل دیدن است.
echo     

exit 0
# در مقایسه با ‎C‎، یک متغیر ‎Bash‎ تعریف شده داخل یک تابع فقط موقعی
#+                   محلی است که به همین صورت (محلی) تعریف بشود.

Caution

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

#!/bin/bash

func ()
{
global_var=37    #  قبل از اینکه تابع فراخوانی شده باشد
                 #+   فقط داخل بلوک تابع قابل دیدن است.
}                #  پایان تابع

echo "global_var = $global_var"       #    global_var =
                 #  تابع ‎func‎ هنوز فراخوانی نگردیده است
                 #+ پس ‎global_var‎ اینجا قابل دیدن نیست.

func
echo "global_var = $global_var"       # global_var = 37
                 # در اثر احضار تابع برقرار گردیده است.


به طوریکه ‎Evgeniy Ivanov‎ توضیح می‌دهد، موقع تعریف نمودن و تنظیم کردن متغیر محلی در یک فرمان منفرد، ظاهراً ترتیب عملیات به این صورت است که اول تنظیم متغیر، و فقط پس از آن محدود نمودنش به حوزه محلی انجام می‌شود. این مورد در مقدار برگشتی منعکس می‌گردد.

#!/bin/bash

echo "==OUTSIDE Function (global)=="
t=$(exit 1)
echo $?      #  1
             #مطابق انتظار.
echo

function0 ()
{

echo "==INSIDE Function=="
echo "Global"
t0=$(exit 1)
echo $?      #  1
             #مطابق انتظار.
echo
echo "Local declared & assigned in same command."
local t1=$(exit 1)
echo $?      #  0
             #  غیر منتظره!
# ظاهراً، تخصیص متغیر قبل از تعریف محلی رخ می‌دهد.
#+       مقدار برگشتی مربوط به بعد از تخصیص است.

echo
echo "Local declared, then assigned (separate commands)."
local t2
t2=$(exit 1)
echo $?      #  1
             #مطابق انتظار.
}

function0

‎24.2.1‎- متغیرهای محلی و بازگشت.

متغیرهای محلی ابزار مفیدی برای نوشتن کد بازگشتی هستند، اما به طور کلی این شیوه، پردازش محاسباتی بسیار زیادی را در بر می‌گیرد و به طور قطع در یک اسکریپت پوسته پیشنهاد نمی‌شود. ‎[6]‎

مثال ‎24-15‎. بازگشت، با استفاده از یک متغیر محلی

#!/bin/bash

#  فاکتوریل
#---------

#           آیا bash بازگشت را اجازه می‌دهد؟
# خب، بله، اما... چنان آهسته است که باید مخ
# شما تاب داشته باشد تا آن را به کار ببرید.

MAX_ARG=5
E_WRONG_ARGS=85
E_RANGE_ERR=86


if [ -z "$1" ]
then
  echo "Usage: `basename $0` number"
  exit $E_WRONG_ARGS
fi

if [ "$1" -gt $MAX_ARG ]
then
  echo "Out of range ($MAX_ARG is maximum)."
  #            اکنون اجازه بدهید واقع‌بین باشیم.
  #  اگر محدوده‌‌ای بزرگتر از این می‌خواهید، آن را
  #+در یک زبان برنامه‌نویسی واقعی بازنویسی کنید.
  exit $E_RANGE_ERR
fi  

fact ()
{
  local number=$1
  #    متغیر ‎number‎ باید به صورت محلی تعریف شده
  #+      باشد، در غیر اینصورت، این کار نمی‌کند.
  if [ "$number" -eq 0 ]
  then
    factorial=1    # فاکتوریل صفر مساوی یک است.
  else
    let "decrnum = number - 1"
    fact $decrnum  #فراخوانی تابع بازگشتی (تابع خودش را احضار می‌کند).
    let "factorial = $number * $?"
  fi

  return $factorial
}

fact $1
echo "Factorial of $1 is $?."

exit 0

همچنین برای مثالی از بازگشت در یک اسکریپت، مثال ‎A-15‎ را ببینید. آگاه باشید که بازگشت به شدت منابع سیستم را مصرف می‌کند و به طور آهسته اجرا می‌شود، و بنابراین به طور کلی در یک اسکریپت مناسب نیست.

یادداشت‌ها

‎[1]‎

اما، به طوریکه ‎Thomas Braunberger‎ توضیح می‌دهد، متغیر محلی تعریف شده در یک تابع، برای توابع فراخوانی شده توسط آن تابع نیز قابل دیدن است.

#!/bin/bash

function1 ()
{
  local func1var=20

  echo "Within function1, \$func1var = $func1var."

  function2
}

function2 ()
{
  echo "Within function2, \$func1var = $func1var."
}

function1

exit 0

#         خروجی اسکریپت:

# Within function1, $func1var = 20.
# Within function2, $func1var = 20.

این مطلب در راهنمای ‎Bash‎ مستند گردیده است:

«منطقه فقط در داخل یک تابع می‌تواند استفاده شود، این باعث می‌شود نام یک متغیر دارای حوزه قابل رویت محدود شده به آن تابع و فرزندان آن باشد.» ‎[تاکید اضافه شده]‎ نگارنده راهنمای ABS تصور می‌کند این رفتار یک باگ باشد.

‎[2]‎

به طور دیگر حشو شناخته می‌شود.

‎[3]‎

به طریقی دیگر همانگویی نامیده می‌شود.

‎[4]‎

به بیان دیگر یک استعاره گفته می‌شود.

‎[5]‎

به طور دیگر یک تابع بازگشتی نامیده می شود.

‎[6]‎

تعداد زیاد مراحل بازگشت می‌تواند باعث از کار افتادن اسکریپت با یک خطای segfault بشود.
مترجم: segfault عبارت است از تلاش یک برنامه در حال اجرا، برای دستیابی به حافظه‌ای که به آن تخصیص داده نشده (حافظه‌ای بیش از پشته تنظیم شده)، که موجب خاتمه یافتن آن برنامه همراه با خطا می‌گردد.

#!/bin/bash
#     اخطار: اجرای این اسکریپت می‌تواند موجب قفل کردن احتمالی سیستم شما بشود!
# اگر خوش شانس باشید، قبل از مصرف کردن تمام حافظه معتبر segfault خواهد نمود.

recursive_function ()		   
{
echo "$1"         # باعث می‌شود تابع کاری انجام بدهد و segfault را شتاب بدهد.
(( $1 < $2 )) && recursive_function $(( $1 + 1 )) $2;
#      مادامیکه پارامتر اول کوچکتر از پارامتر دوم است، افزایش اولی و بازگشت.
}

recursive_function 1 50000         #                   بازگشت ‎ 50,000‎ مرحله!
#به احتمال بسیار segfault می‌کند (به پشته تنظیم شده با ‎ulimit -m‎ بستگی دارد).

#   بازگشت با این عمق حتی شاید با مصرف تمام حافظه اختصاص یافته به پشته، باعث
#+                                             segfault یک برنامه C نیز بشود.

echo "This will probably not print."
exit 0                             # این اسکریپت به طور عادی خارج نخواهد شد.
#با تشکر از ‎Stéphane Chazelas‎.