توابع در Bash بلوکهای کدی هستند که یک عملیات مشخص را انجام میدهند و میتوان آنها را با یک نام صدا زد. استفاده از توابع، اسکریپت را ماژولار، خواناتر و قابل نگهداریتر میکند؛ بهجای تکرار یک قطعه کد در چند جا، آن را درون یک تابع تعریف کرده و در هر نقطه که نیاز است، فراخوانی میکنیم.
تعریف توابع
Bash سه سبک سینتکسی برای تعریف تابع ارائه میدهد که دو تای اول رایجترند.
سبک اول (استاندارد POSIX — قابلحملترین):
name() { commands }
سبک دوم (اختصاصی Bash — استفاده از کلمهی کلیدی function):
function name { commands }
سبک سوم (ترکیبی — در Bash پشتیبانی میشود اما غیراستاندارد است):
function name() { commands }
اگرچه سبک سوم کار میکند، اما استفاده از آن توصیه نمیشود؛ چراکه سردرگمی ایجاد کرده و در برخی از شلهای مشابه (مثل
sh) پشتیبانی نمیشود. سبک اول (name() {}) بهترین انتخاب برای اسکریپتهای قابلحمل و سبک دوم (function name {}) برای اسکریپتهای اختصاصی Bash رایج است.
نکتهی سینتکسی حیاتی: بین name() و { باید فاصله یا خط جدید وجود داشته باشد. نوشتن name(){ بدون فاصله، در برخی نسخهها کار میکند اما استاندارد نیست و میتواند باعث خطا شود.
#!/bin/bash # تعریف تابع say_hello() { echo "سلام، به اسکریپت خوش آمدید!" } # فراخوانی تابع say_hello
قوانین مهم در تعریف توابع
-
توابع باید قبل از اولین فراخوانی تعریف شوند.
-
بدنهی تابع میتواند شامل هر دستور معتبر Bash باشد: شرط، حلقه، فراخوانی توابع دیگر، و حتی تعریف توابع تو در تو (هرچند توصیه نمیشود).
-
برای حذف (unset) یک تابع از
unset -f function_nameاستفاده کنید.
آرگومانها در توابع
وقتی یک تابع را فراخوانی میکنید، میتوانید آرگومانهایی به آن بدهید. این آرگومانها درون تابع بهصورت پارامترهای موقعیتی در دسترس هستند، دقیقاً مشابه آرگومانهای اسکریپت — با این تفاوت که این پارامترها مختص به همان تابع هستند و پارامترهای موقعیتی اسکریپت اصلی را تحت تأثیر قرار نمیدهند.
| پارامتر ویژه درون تابع | توضیح |
|---|---|
$1, $2, ... | آرگومانهای اول، دوم و ... که به تابع داده شدهاند |
$@ | تمام آرگومانهای تابع بهصورت مجزا (استفاده با "$@" توصیه میشود) |
$* | تمام آرگومانهای تابع بهصورت یک رشته (با "$*") |
$# | تعداد آرگومانهای ارسالشده به تابع |
$0 | همچنان نام اسکریپت اصلی است (و نه نام تابع) |
مثال:
#!/bin/bash greet_user() { echo "سلام، $1 عزیز!" echo "شما $2 سال دارید." echo "تعداد آرگومانهای دریافتی: $#" } greet_user "علی" ۲۸
خروجی:
سلام، علی عزیز! شما ۲۸ سال دارید. تعداد آرگومانهای دریافتی: ۲
دریافت تمام آرگومانها با "$@"
درست مانند اسکریپت اصلی، "$@" درون یک تابع، تمام آرگومانهای دادهشده به آن تابع را بهصورت جداگانه و با حفظ فاصلههای درونرشتهای نگه میدارد.
#!/bin/bash print_all() { for arg in "$@"; do echo "آرگومان: $arg" done } print_all "سلام دنیا" Bash "scripting is fun"
خروجی:
آرگومان: سلام دنیا آرگومان: Bash آرگومان: scripting is fun
وضعیت بازگشت (Return Status)
هر تابع در Bash میتواند یک کد خروج (مقدار صحیح بین ۰ تا ۲۵۵) به فراخواندهنده بازگرداند. این کار با دستور return انجام میشود.
return [n]
-
اگر
returnبدون آرگومان داده شود، کد خروج آخرین دستور اجراشده در تابع بازگردانده میشود. -
مقدار
0به معنای موفقیت و هر مقدار غیرصفر نشاندهندهی یک نوع خطا یا وضعیت خاص است. -
کد خروج تابع بلافاصله پس از فراخوانی در متغیر ویژهی
$?در دسترس است.
مثال:
#!/bin/bash is_even() { local num=$1 if (( num % 2 == 0 )); then return 0 # زوج → موفقیت else return 1 # فرد → شکست fi } is_even 4 if [[ $? -eq 0 ]]; then echo "عدد ۴ زوج است." else echo "عدد ۴ فرد است." fi is_even 7 if [[ $? -eq 0 ]]; then echo "عدد ۷ زوج است." else echo "عدد ۷ فرد است." fi
تفاوت return با exit
| دستور | محدودهی اثر |
|---|---|
return | فقط از تابع فعلی خارج میشود و کنترل را به فراخواندهنده برمیگرداند. |
exit | کل اسکریپت (شل جاری) را خاتمه میدهد. |
هشدار: هرگز از
exitدرون یک تابع برای نشاندادن شکست استفاده نکنید، مگر اینکه واقعاً قصد پایان دادن به کل اسکریپت را داشته باشید. استفادهی نادرست ازexitدر توابع، اسکریپت را بهطور کامل متوقف میکند و فرصت مدیریت خطا را از فراخواندهنده میگیرد.
بازگرداندن مقدار رشتهای (خروجی متنی)
مهمترین نکته در مورد return این است که فقط اعداد صحیح بین ۰ تا ۲۵۵ را بازمیگرداند. برای بازگرداندن یک رشته یا مقدار غیرعددی، باید از خروجی استاندارد (stdout) استفاده کنید و آن را با جانشینی دستورات ($(...)) بگیرید:
#!/bin/bash get_date() { echo "$(date +%Y-%m-%d)" # خروجی به stdout میرود } today=$(get_date) # خروجی تابع در متغیر ذخیره میشود echo "امروز: $today"
#!/bin/bash sum() { echo $(( $1 + $2 )) # نتیجه را چاپ میکند } result=$(sum 10 20) echo "مجموع: $result" # خروجی: مجموع: 30
متغیر محلی (local)
بهطور پیشفرض، همهی متغیرها در Bash سراسری (global) هستند — حتی آنهایی که درون یک تابع تعریف میشوند. این یعنی تغییر یک متغیر درون تابع، بر همان متغیر در بیرون تابع نیز تأثیر میگذارد.
برای جلوگیری از این رفتار و ایجاد متغیرهایی که فقط درون همان تابع معتبر باشند، از کلمهی کلیدی local استفاده میکنیم.
local var_name=value
مثال مقایسهای (بدون local — خطرناک):
#!/bin/bash name="علی" # متغیر سراسری change_name() { name="رضا" # این همان متغیر سراسری را تغییر میدهد echo "داخل تابع: $name" } echo "قبل از تابع: $name" # خروجی: علی change_name # خروجی: داخل تابع: رضا echo "بعد از تابع: $name" # خروجی: رضا (متغیر سراسری تغییر کرده!)
مثال با local (صحیح):
#!/bin/bash name="علی" safe_change() { local name="رضا" # این یک متغیر محلی جدید است echo "داخل تابع: $name" } echo "قبل از تابع: $name" # خروجی: علی safe_change # خروجی: داخل تابع: رضا echo "بعد از تابع: $name" # خروجی: علی (متغیر سراسری دست نخورده باقی میماند)
هشدار جدی: فراموشکردن
localبرای متغیرهای درون تابع، یکی از رایجترین اشتباهات در اسکریپتنویسی Bash است و میتواند باعث بروز باگهای بسیار سختیاب شود، مخصوصاً در اسکریپتهای بزرگ. قاعدهی طلایی: درون هر تابعی، تمام متغیرهای موقتی را باlocalتعریف کنید.
گستره (Scope) در توابع
درک گسترهی متغیرها در Bash، با زبانهای معمول (مثل C یا Python) تفاوت دارد و از گسترهی پویا (Dynamic Scoping) پیروی میکند. به این معنا که یک تابع میتواند به متغیرهای تعریفشده در تابع فراخواندهنده دسترسی داشته باشد، مگر اینکه آنها را با local در سطح خودش مخفی کند.
مثال: دسترسی به متغیر تابع بیرونی
#!/bin/bash outer() { local outer_var="من از بیرون هستم" inner } inner() { echo "$outer_var" # اینجا به متغیر outer دسترسی دارد چون outer تابع فراخواندهنده است } outer # خروجی: من از بیرون هستم
اگر inner را خارج از outer صدا بزنیم، outer_var تعریف نشده است و خطا نمیدهد بلکه خالی چاپ میشود.
مثال: مخفیکردن با local:
#!/bin/bash x=10 first() { local x=20 second } second() { echo "x در تابع second: $x" # اینجا به متغیر first دسترسی دارد که ۲۰ است } first # خروجی: x در تابع second: 20 echo "x در سطح جهانی: $x" # خروجی: 10 (چون در first فقط local بود)
ارسال متغیرها به توابع
روش استاندارد برای دادن داده به تابع، استفاده از پارامترهای موقعیتی ($1, $2, ...) است. استفاده از متغیرهای سراسری درون توابع، خوانایی را کاهش میدهد و وابستگیهای پنهان ایجاد میکند.
# روش خوب calculate_area() { local width=$1 local height=$2 echo $(( width * height )) } # روش بد (وابسته به متغیرهای سراسری) width=5 height=3 calculate_area_bad() { echo $(( width * height )) # به متغیرهای بیرون وابسته است }
تکنیکهای پیشرفته
۱. ارسال آرایه به تابع
آرایهها را نمیتوان مستقیماً بهعنوان آرگومان به تابع داد (چون گسترش مییابند). دو راه حل رایج وجود دارد:
راهحل اول: ارسال بهصورت لیست و بازسازی در تابع
#!/bin/bash process_array() { local arr=("$@") # تمام آرگومانها را بهعنوان آرایه دریافت کن for item in "${arr[@]}"; do echo "عنصر: $item" done } my_array=("apple" "banana" "cherry") process_array "${my_array[@]}"
راهحل دوم: استفاده از ارجاع به نام (Name References) در Bash 4.3+
#!/bin/bash print_array() { local -n arr=$1 # ارجاع به نام متغیر for item in "${arr[@]}"; do echo "$item" done } fruits=("apple" "banana" "cherry") print_array fruits
local -nیک مرجع (reference) به متغیر اصلی ایجاد میکند؛ تغییرات رویarrروی آرایهی اصلی نیز اعمال میشود.
۲. توابع بازگشتی (Recursion)
توابع در Bash میتوانند خودشان را فراخوانی کنند. یک مثال کلاسیک، محاسبهی فاکتوریل است:
#!/bin/bash factorial() { local n=$1 if (( n <= 1 )); then echo 1 else local sub=$(factorial $(( n - 1 ))) echo $(( n * sub )) fi } result=$(factorial 5) echo "فاکتوریل ۵: $result" # خروجی: 120
در توابع بازگشتی، استفادهی صحیح از
localبسیار حیاتی است تا متغیرهای هر سطح از فراخوانی با یکدیگر تداخل نکنند.
۳. declare در توابع
دستور declare نیز میتواند برای تعریف متغیرهای محلی استفاده شود (declare درون تابع، رفتار مشابه local دارد)، اما local بهوضوح برای این منظور طراحی شده است و خوانایی بهتری دارد.
my_func() { declare var="value" # معادل local، اما در نسخههای قدیمی Bash ممکن است متفاوت باشد }
اشتباهات رایج
| اشتباه | توضیح | راهحل |
|---|---|---|
فراموشی local | متغیرهای درون تابع، متغیرهای سراسری را آلوده میکنند | همیشه از local استفاده کنید |
استفاده از exit بهجای return | کل اسکریپت خاتمه مییابد نه فقط تابع | از return برای بازگشت از تابع استفاده کنید |
فراموشی فاصله قبل از { | خطای سینتکسی syntax error near unexpected token | بین نام تابع و { فاصله بگذارید |
تلاش برای return کردن رشته | return فقط عدد ۰-۲۵۵ قبول میکند | از echo و $(...) برای بازگرداندن رشته استفاده کنید |
| استفاده از متغیر سراسری برای آرگومان | کد را وابسته و غیرقابلپیشبینی میکند | آرگومانها را بهصورت $1, $2 دریافت کنید |
جمعبندی سریع دستورات و مفاهیم
| مفهوم | دستور/سینتکس | کاربرد |
|---|---|---|
| تعریف تابع | func() { ... } | تعریف یک تابع جدید |
| فراخوانی تابع | func arg1 arg2 | اجرای تابع با آرگومانها |
| آرگومانهای تابع | $1, $2, "$@" | دسترسی به آرگومانهای دادهشده |
| تعداد آرگومانها | $# | تعداد آرگومانهای تابع |
| بازگشت کد خروج | return 0 | بازگرداندن وضعیت (۰=موفقیت) |
| بازگشت رشته | echo "result" و $(func) | گرفتن خروجی متنی تابع |
| متغیر محلی | local var=value | تعریف متغیر با محدودهی تابع |
| حذف تابع | unset -f func | پاککردن تعریف تابع از حافظه |