ال

الدوال في Bash

{zero: "لا توجد تمارين", one: "1 تمرين", two: "تمرينان", few: "%{count} تمارين", many: "%{count} تمرينًا", other: "%{count} تمرين"}

نبذة عن الدوال

تُكتب كثير من سكربتات Bash بأسلوب أمري صرف: نفّذ أمرًا، ثم نفّذ أمرًا آخر، وهكذا. أحيانًا تحتاج إلى تجميع مجموعة من الأوامر تؤدي معًا غرضًا واحدًا. وهنا يأتي دور الدوال.

تعريف دالة

تُعرّف الدالة هكذا:

my_function () {
    COMMANDS
}

مجموعة الأقواس الهلالية الفارغة تشير ببساطة إلى أنك تُعرّف دالة. ولا يوضع داخلها شيء.

معاملات الدالة

الدوال، بعد تعريفها، تتصرّف مثل أي أمر آخر (سواء كان مدمجًا أم لا). وكما هو الحال مع أي أمر، يمكنك تمرير الوسائط إلى دوالك. وداخل الدالة، تصل إلى الوسائط باستخدام المعاملات الموضعية، $1، $2، وهكذا. (تذكّر أننا تعلمنا المعاملات الموضعية في مفهوم المتغيرات.)

Advanced

لا يتغيّر المعامل الخاص $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

داخل الدالة، يمكنك الوصول إلى المتغيرات الموجودة في نطاق المستدعي. وهذا يعني أنك تستطيع استخدام المتغيرات العامة، وكذلك المتغيرات المحلية التي أُعلنت في المستدعي (أو في دالة ما تستدعي ذلك المستدعي).

Advanced

من الناحية التقنية، كلمة "عام" ليست التسمية الدقيقة هنا. لتوسيع متغير داخل دالة، تنتقل 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

وبالمثل، فإن إسناد قيمة إلى متغير يُسنِده في النطاق الذي أُعلن فيه. هذا "التأثير عن بُعد" قد يُنتج كودًا يصعب تتبّعه، إذ ليس من الواضح دائمًا أين أُسندت القيمة إلى المتغير.

Advanced

يمكن فحص مكدّس الاستدعاء باستخدام متغير المصفوفة 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 دون وسائط يُرجع حالة آخر أمر نُفّذ.

Note

لاحظ أنه يمكن تبسيط الدالة check_password إلى:

check_password () { [[ $1 == "secret" ]]; }
  1. البنية الشرطية [[...]] لها حالة خروج: 0 تعني "صحيح"، و1 تعني "خطأ".
  2. يجب أن يكون قبل القوس الختامي في بنية التجميع {...} سطر جديد أو فاصلة منقوطة.

مُخرجات الدالة

حالة إرجاع الدالة ليست سوى عدد. فكيف يمكن للدالة أن تُنتج مخرجات؟

يمكن لدالتك أن تطبع على المخرج القياسي. استخدم استبدال الأوامر المألوف لالتقاطه:

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
Advanced

يمكن التحكم في عمق الاستدعاء الذاتي باستخدام المتغير 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)
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة