اذهب إلى المحتوى

تعلمنا في الجزء الأول من بُنى التحكم عن الأمر if وكيف يُستخدم لتعديل مسار تنفيذ البرنامج بناءً على حالة خروج أحد الأوامر. يسمى هذا النوع من مسارات تنفيذ البرامج -وفق الاصطلاحات البرمجية- بالتفرّع (branching) لأنَّه يشبه كثيرًا التنقل في شجرة، فعندما تصل إلى تفرّع، فستُحدِّد نتيجة الشرط أيَّ فرعٍ ستذهب إليه. 

shell-script-case-while-until.png

هنالك نوعٌ آخرٌ أكثر تعقيدًا من أنواع التفرع يُسمى case، وهنالك عدِّة خيارات في case، فعلى عكس التفرّع البسيط الذي فيه طريقان فقط، يمكن أن تكون هنالك عدِّة نواتج بناءً على تحقيق شرط معيّن لقيمةٍ ما. 

يمكنك بناء مثل هذا النوع من أنواع التفرّع باستخدام عدِّة جمل if شرطية. سنحاول في المثال الآتي التحقق من مدخلات المستخدم:

#!/bin/bash

echo -n "Enter a number between 1 and 3 inclusive > "
read character
if [ "$character" = "1" ]; then
    echo "You entered one."
elif [ "$character" = "2" ]; then
    echo "You entered two."
elif [ "$character" = "3" ]; then
    echo "You entered three."
else
    echo "You did not enter a number between 1 and 3."
fi

لكن ما سبق ليس كفؤًا كما كنا نتوقع، ولكن لحسن الحظ، توفِّر الصَدَفة لنا حلًا أفضل لهذه المشكلة: الأمر المُضمَّن ‏case، الذي يمكن استخدامه لبناء برنامج مكافئ تمامًا للبرنامج السابق:

#!/bin/bash

echo -n "Enter a number between 1 and 3 inclusive > "
read character
case $character in
    1 ) echo "You entered one."
        ;;
    2 ) echo "You entered two."
        ;;
    3 ) echo "You entered three."
        ;;
    * ) echo "You did not enter a number between 1 and 3."
esac

الشكل العام للأمر case:

case word in
    patterns ) commands ;;
esac

يُنفِّذ الأمر case عباراتٍ برمجيةً معيّنة إذا طابَقت الكلمة نمطًا (pattern)، يمكنك وضع أي عدد من الأنماط والعبارات. ويمكن أن تكون الأنماط نصًا عاديًا أو "محارف بديلة" (wildcards). يمكن وضع أكثر من نمط في نفس السطر بفصل الأنماط بمحرف |. هذا مثالٌ متقدمٌ يشرح لك ما أعنيه بكلامي السابق:

#!/bin/bash

echo -n "Type a digit or a letter > "
read character
case $character in
                                # Check for letters
    [[:lower:]] | [[:upper:]] ) echo "You typed the letter $character"
                                ;;

                                # Check for digits
    [0-9] )                     echo "You typed the digit $character"
                                ;;

                                # Check for anything else
    * )                         echo "You did not type a letter or a digit"
esac

لاحظ النمط الخاص * الذي سيُطابِق أي شيء، لذلك سنحتاج إليه لمطابقة الحالات التي لم تُطابِقها الأنماط التي تسبقه. من المستحسن تضمين هذا النمط في نهاية الأمر case لكي يُستخدَم لكشف المدخلات غير الصحيحة.

حلقات التكرار

آخر جزء من بُنى التحكم التي سنناقشها هي حلقات التكرار، تُنفِّذ حلقات التكرار قسمًا من البرنامج لعددٍ من المرات وذلك بناءً على حالة خروج أحد الأوامر. توفِّر الصَدَفة ثلاثة أوامر للتكرار: while و until و for، وسنشرح while و until في هذا الدرس وسنترك for لدرسٍ لاحق. 

يؤدي الأمر while إلى تنفيذ كتلة من الشيفرة مرارًا وتكرارًا لطالما كانت حالة خروج الأمر المُحدَّد true (أي حالة الخروج 0). هذا مثالٌ بسيطٌ يعد الأرقام من 0 إلى 9:

#!/bin/bash

number=0
while [ "$number" -lt 10 ]; do
    echo "Number = $number"
    number=$((number + 1))
done

أنشأنا في السطر الثالث متغيرًا وأسميناه number وأسندنا القيمة 0 إليه، ثم بدأنا حلقة while. وكما لاحظت، كان الأمر التي ستختبر حلقة while حالة خروجه هو الأمر test الذي يختبر قيمة العدد number. وفي مثالنا السابق، اختبرنا إذا كانت قيمة number أصغر من 10. 

لاحظ الكلمة do في السطر الرابع والكلمة done في السطر السابق. هاتان الكلمتان تحيطان بالشيفرة التي ستُكرَّر لطالما كانت حالة خروج الأمر المُحدَّد مساويةً للصفر. 

في غالبية الحالات، تقوم الشيفرة التي ستُكرَّر بشيءٍ ما سيؤدي في نهاية المطاف إلى تغيير حالة خروج الأمر المُحدَّد، وإلا فستكون لديك حلقة تكرار لا نهائية؛ أي حلقةُ تكرارٍ لا يتوقف تنفيذها أبدًا. 

تطبع الشيفرة التي ستُكرَّر في المثال السابق قيمة المتغير number (الأمر echo في السطر الخامس)، ثم تُزيد قيمة number بمقدار 1 في السطر السادس. وفي كل مرة يكتمل فيها تنفيذ الشيفرة، فسيتم التحقق من قيمة حالة الخروج للأمر test، وبعد التكرار العاشر للحلقة، فسيزداد المتغير number عشر مرات وسينتهي الأمر test بحالة خروج لا تساوي الصفر. وفي هذه المرحلة، سيُكمِل البرنامج مساره بتنفيذ التعليمات البرمجية التي تلي الكلمة done، لكن ولمّا كانت done هي آخر كلمة في مثالنا، فسينتهي تنفيذ البرنامج فورًا. 

يعمل الأمر until بشكلٍ مماثل، عدا أنَّ الشيفرة ستُكرَّر لطالما كانت حالة خروج الأمر المُحدَّد false (أي ليست 0). لاحظ تغيير التعبير الذي مررناه للأمر test لكي يعمل بشكلٍ مماثل للمثال السابق عن حلقة while:

#!/bin/bash

number=0
until [ "$number" -ge 10 ]; do
    echo "Number = $number"
    number=$((number + 1))
done

بناء قائمة

طريقة شائعة لتوفير واجهة للمستخدم للبرامج النصيّة (أي التي تعمل من سطر الأوامر) هي استخدام القوائم. القائمة هي سلسلة من الخيارات التي يستطيع المستخدم انتقاء واحدة منها. 

سنستخدم ما تعلمناه سابقًا عن الحلقات وعبارة case لبناء برنامج بسيط فيه قائمة:

#!/bin/bash

selection=
until [ "$selection" = "0" ]; do
    echo "
    PROGRAM MENU
    1 - Display free disk space
    2 - Display free memory

    0 - exit program
"
    echo -n "Enter selection: "
    read selection
    echo ""
    case $selection in
        1 ) df ;;
        2 ) free ;;
        0 ) exit ;;
        * ) echo "Please enter 1, 2, or 0"
    esac
done

الغرض من حلقة until هو إعادة عرض القائمة في كل مرة يتم اختيار عنصر من القائمة فيها. ستتكرر الحلقة إلى أن يختار المستخدم 0 (خيار الخروج).

لاحظ ماذا سنفعل إذا لم تكن مدخلات المستخدم صحيحة (1 أو 2 أو 0). 

نستطيع إضافة دالة تطلب من المستخدم الضغط على الزر Enter بعد عرض كل خيار لتحسين طريقة عرض البرنامج عند تشغيله، وذلك بمسح الشاشة قبل إعادة عرض القائمة مرةً أخرى. هذا هو المثال المُحسَّن:

#!/bin/bash

press_enter()
{
    echo -en "\nPress Enter to continue"
    read
    clear
}

selection=
until [ "$selection" = "0" ]; do
    echo "
    PROGRAM MENU
    1 - display free disk space
    2 - display free memory

    0 - exit program
"
    echo -n "Enter selection: "
    read selection
    echo ""
    case $selection in
        1 ) df ; press_enter ;;
        2 ) free ; press_enter ;;
        0 ) exit ;;
        * ) echo "Please enter 1, 2, or 0"; press_enter
    esac
done

عندما "يُعلِّق" حاسوبك … 

لقد مررنا جميعًا بتجربة "تعليق" أحد البرامج، الذي يحدث عندما يتوقف البرنامج عن العمل ولا يستجيب لأي شيء. ربما تظن أنَّ البرنامج قد توقف، إلا أنَّه -في معظم الحالات- ما يزال يعمل لكن واجهته مشكلةٌ في البنية المنطقية فيه وقد دخل في حلقة تكرار لا نهائية. 

تخيل هذه الحالة: لنقل أنَّك وصلت جهازًا خارجيًا إلى حاسوبك، مثل قرص USB، لكنك نسيت تشغيله. ثم حاولت استخدام الجهاز لكن التطبيق علّق، فعندما يحدث ذلك، تستطيع أن تتخيل المحادثة الآتية بين التطبيق والمنفذ الذي يتصل عبره الجهاز:

اقتباس

التطبيق: هل أنت جاهز؟ 
المنفذ: الجهاز غير جاهز.

التطبيق: هل أنت جاهز؟ 
المنفذ: الجهاز غير جاهز.

التطبيق: هل أنت جاهز؟ 
المنفذ: الجهاز غير جاهز.

التطبيق: هل أنت جاهز؟ 
المنفذ: الجهاز غير جاهز. 

وسيستمر الأمر هكذا إلى ما لا نهاية … 

البرامج المكتوبة بطريقة جيدة تتفادى الحالة السابقة بضبط مهلة زمنية (timeout)، وهذا يعني أنَّ حلقة التكرار تحسب عدد المحاولات أو تحسب مقدار الزمن الذي انتظرته لكي يحدث شيءٌ مُحدَّد. وإن تجاوز عدد المحاولات أو الزمن المُنتَظَر حدًّا معيّنًا، فسينتهي تنفيذ الحلقة وسيولِّد البرنامج رسالة خطأ. 

ترجمة -وبتصرّف- للمقال Flow Control - Part 2 لصاحبه William Shotts.


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...