تُكتب كثير من سكربتات Bash بأسلوب أمري صرف: نفّذ أمرًا، ثم نفّذ أمرًا آخر، وهكذا. أحيانًا تحتاج إلى تجميع مجموعة من الأوامر تؤدي معًا غرضًا واحدًا. وهنا يأتي دور الدوال.
تُعرّف الدالة هكذا:
my_function () {
COMMANDS
}
مجموعة الأقواس الهلالية الفارغة تشير ببساطة إلى أنك تُعرّف دالة. ولا يوضع داخلها شيء.
الدوال، بعد تعريفها، تتصرّف مثل أي أمر آخر (سواء كان مدمجًا أم لا).
وكما هو الحال مع أي أمر، يمكنك تمرير الوسائط إلى دوالك.
وداخل الدالة، تصل إلى الوسائط باستخدام المعاملات الموضعية، $1، $2، وهكذا.
(تذكّر أننا تعلمنا المعاملات الموضعية في مفهوم المتغيرات.)
لا يتغيّر المعامل الخاص $0 داخل الدالة؛ فهو يبقى اسم السكربت الذي يجري تنفيذه.
وتستطيع الدالة التي يجري تنفيذها حاليًا الوصول إلى اسمها عبر المتغير $FUNCNAME.
انظر إلى قسم 3.4.2 المعاملات الخاصة في الدليل.
يمكنك تعريف متغيرات داخل دالة.
إذا أعلنت المتغيرات بالأمر 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)