بسیاری از اسکریپتهای Bash با سبک کاملاً امری نوشته میشوند: یک دستور را اجرا کنید، بعد دستور دیگری را اجرا کنید و به همین ترتیب. گاهی لازم است دنبالهای از دستورها را کنار هم بگذارید که در مفهوم یک هدف واحد را انجام میدهند. اینجاست که «توابع» به کار میآیند.
یک تابع را اینطور تعریف میکنید:
my_function () {
COMMANDS
}
مجموعهی خالی پرانتزها فقط نشان میدهد که دارید یک تابع تعریف میکنید. چیزی داخل آنها قرار نمیگیرد.
توابع، پس از تعریف، مثل هر دستور دیگری عمل میکنند (توکار باشد یا نباشد).
مثل هر دستور دیگری، میتوانید «آرگومانها» را به تابعهایتان بدهید.
داخل تابع، با استفاده از «پارامترهای موقعیتی» یعنی $1، $2 و غیره به آرگومانها دسترسی دارید.
(به یاد بیاورید که دربارهی پارامترهای موقعیتی در مفهوم متغیر خواندیم.)
پارامتر ویژهی $0 داخل تابع تغییر نمیکند؛ همچنان اسم اسکریپتی است که در حال اجراست.
تابعی که در حال اجراست میتواند با متغیر $FUNCNAME به اسم خودش دسترسی داشته باشد.
بخش ۳٫۴٫۲ پارامترهای ویژه را در راهنما ببینید.
میتوانید داخل یک تابع متغیر تعریف کنید.
اگر متغیر را با دستور local اعلان کنید، «دامنهی» متغیر به تابع جاری محدود میشود (و به هر تابعی که توسط آن فراخوانی شود).
در غیر این صورت، متغیر در «دامنهی سراسری» قرار میگیرد.
متغیرهای محلی میتوانند اسمی یکسان با یک متغیر سراسری داشته باشند. در آن صورت، متغیر محلی بر متغیر سراسری سایه میاندازد. برای نمونه، متغیر محلیای که در یک تابع اعلان شده، متغیر سراسری همنام را پنهان میکند: ارجاعها و انتسابها به متغیر محلی اشاره دارند و متغیر سراسری بدون تغییر میماند. وقتی تابع برمیگردد، متغیر سراسری دوباره قابل مشاهده میشود.
x=5
myfunc () {
local x=100
echo "in my function, $x == 100"
}
echo "in the global scope, $x == 5"
myfunc
echo "back in the global scope, $x == 5"
خروجی این است
in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5
داخل یک تابع، میتوانید به متغیرهای موجود در دامنهی «فراخواننده» دسترسی داشته باشید. یعنی میتوانید از متغیرهای سراسری استفاده کنید و همچنین از متغیرهای محلیای که در فراخواننده (یا در تابعی که فراخواننده را فراخوانی میکند) اعلان شدهاند.
از نظر فنی، «سراسری» واژهی درستی نیست. برای بسط دادن یک متغیر در یک تابع، Bash «پشتهی فراخوانی» را تا دامنهی سراسری بالا میرود تا تابعی را پیدا کند که اسم آن متغیر در آن اعلان شده است.
این مثال از بخش توابع پوسته در راهنما برگرفته شده است:
func1() {
local var='func1 local'
func2
}
func2() {
echo "In func2, var = $var"
}
var=global
func1
func2
خروجی چنین است:
In func2, var = func1 local
In func2, var = global
بههمینترتیب، وقتی مقداری را به یک متغیر انتساب میدهید، این انتساب در همان دامنهای که متغیر اعلان شده است انجام میشود. این «کنش از راه دور» میتواند کدی سختفهم بسازد، چون همیشه روشن نیست که مقدار یک متغیر کجا انتساب داده شده است.
میتوان پشتهی فراخوانی را با متغیر آرایهای FUNCNAME بررسی کرد.
یک تابع، مثل هر دستور دیگری، «وضعیت خروج» دارد. بهطور پیشفرض، وضعیت یک تابع همان وضعیت خروج آخرین دستور اجراشده است.
میتوانید از دستور return استفاده کنید تا با یک وضعیت خروج مشخص از تابع برگردید.
check_password () {
if [[ $1 == "secret" ]]; then
return 0
else
return 1
fi
}
read -sp "Enter your password: " pass
if check_password "$pass"; then
echo "Correct!"
else
echo "Wrong password."
fi
استفاده از return بدون آرگومان، وضعیت آخرین دستور اجراشده را برمیگرداند.
توجه کنید که میتوان تابع check_password را به این صورت ساده کرد:
check_password () { [[ $1 == "secret" ]]; }
۱. ساختار شرطی [[...]] یک وضعیت خروج دارد: 0 برای «درست»، 1 برای «غلط».
۲. ساختار گروهبندی {...} باید پیش از آکولاد پایانی، یک خط جدید یا یک نقطهویرگول داشته باشد.
وضعیت بازگشتی یک تابع فقط یک عدد است. پس یک تابع چگونه میتواند خروجی تولید کند؟
تابع شما میتواند در خروجی استاندارد چاپ کند. برای گرفتن آن، از «جانشینی دستور» که با آن آشنا هستید استفاده کنید:
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }
die=$( d6 )
echo "You rolled a $die."
وضعیت خروج یک تابع حتی وقتی خروجی را در متغیری میگیرید هم در دسترس است.
roll () {
local n=$1
if (( 4 <= n && n <= 20 )); then
echo "$(( 1 + RANDOM % n ))" # exit status is 0
else
return 1
fi
}
read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
echo "You rolled a $die."
else
echo "I can't roll a die with $faces faces."
fi
توابع میتوانند خودشان را بهصورت بازگشتی فراخوانی کنند. بهطور پیشفرض، محدودیتی برای عمق بازگشت وجود ندارد.
یک مثال:
fibonacci() {
local n=$1
if (( n <= 1 )); then
echo "1"
else
local a=$(fibonacci "$(( n - 1 ))")
local b=$(fibonacci "$(( n - 2 ))")
echo "$(( a + b ))"
fi
}
for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
عمق بازگشت را میتوان با متغیر FUNCNEST کنترل کرد.
bash -c '
recur() {
echo $1
recur $(($1 + 1))
}
FUNCNEST=5
recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)