مدخل إلى كتابة سكربتات الصدفة كيف تستخدم بنى التحكم (Flow Control) في سكربتات الصدفة (Shell Scripts) - الجزء 1


عبد اللطيف ايمش

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

shell-scripts-if-test-exit.png

توفِّر الصَدَفة عدِّة أوامر يمكننا استخدامها للتحكم في جريان تنفيذ البرنامج، وسنتعلم في هذا الدرس: 

  • if 
  • test 
  • exit

الأمر if

أول أمر سنلقي عليه نظرةً هو الأمر if، يبدو من أول وهلة أنَّ الأمر if بسيط جدًا، لأنه يتخذ قرارًا بناءً على "حالة الخروج" (exit status) لأحد الأوامر. الشكل العام لأمر if:

if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi

حيث commands هي مجموعة أوامر. قد تبدو الأمور مربكةً بعض الشيء في البداية، لكن قبل شرح الأمر if بالتفصيل، لنلقِ نظرة على آلية معرفة الصَدَفة لنجاح أو فشل تنفيذ أمر ما.

حالة الخروج

تُرسِل الأوامر (بما في ذلك السكربتات ودوال الصدفة التي نكتبها) قيمة إلى النظام عند انتهاء تنفيذها تُسمى "حالة الخروج" (exit status)؛ هذه القيمة (والتي هي عدد صحيح يتراوح ما بين 0 و 255) تُشير إلى نجاح أو فشل تنفيذ الأمر. من الأعراف البرمجية أن تكون القيمة 0 تعني نجاح التنفيذ وأي قيمة أخرى تعني فشله. 

توفِّر الصَدَفة معاملًا (parameter) خاصًا لتفحص حالة الخروج لآخر أمر مُنفَّذ:

$ ls -d /usr/bin
/usr/bin
$ echo $?
0
$ ls -d /bin/usr
ls: cannot access /bin/usr: No such file or directory
$ echo $?
2

نفَّذنا -في المثال السابق- الأمر ls مرتين، حيث نُفِّذ تنفيذًا سليمًا في أول مرة، فلو عرضنا قيمة المعامل ‎$?‎ فسنرى أنها تساوي الصفر؛ ثم نفَّذنا الأمر ls مرةً أخرى لكنه عرض رسالة خطأ وعندما طبعنا قيمة المعامل ‎$?‎ مرةً أخرى فسنرى أنَّها تساوي 2، مما يشير إلى أنَّ آخر أمر قد نُفِّذ واجه مشكلةً. 

تستعمل بعض الأوامر حالات خروج مختلفة لتوفير معلومات حول الخطأ، لكن العديد من الأوامر تستعمل الرقم 1 فقط للإشارة إلى باقي أنواع الأخطاء.

تحتوي صفحات الدليل man عادةً على قسم مُعنوَن "Exit Status" يشرح ما هي أرقام حالات الخروج المحتملة، لكن تذكَّر أنَّ 0 تُشير دائمًا إلى نجاح التنفيذ. 

توفِّر الصَدَفة أمرَين مُضمَّنين فيها بسيطين للغاية، كلُ ما يفعلانه هو الانتهاء بحالة خروج 0 أو 1. ينجح تنفيذ الأمر true دائمًا ويفشل تنفيذ الأمر false دائمًا:

$ true
$ echo $?
0
$ false
$ echo $?
1

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

$ if true; then echo "It's true."; fi
It's true.
$ if false; then echo "It's true."; fi
$

يُنفَّذ الأمر echo "It's true."‎ عندما يُنفَّذ الأمر الذي يلي كلمة if بنجاح، ولن يُنفَّذ عندما يفشل تنفيذ الأمر الذي يلي كلمة if.

الأمر test

يُستخدَم الأمر test كثيرًا مع الأمر if لإنتاج القيمة true أو false. هذا الأمر غير اعتيادي لأنَّ له شكلان مختلفان:

# الشكل الأول
test expression

# الشكل الثاني
[ expression ]

آلية عمل الأمر test بسيطةٌ جدًا، فلو كان التعبير (expression) الذي يليه صحيحًا، فسينتهي تنفيذ الأمر test بحالة خروج تساوي الصفر؛ فيما عدا ذلك سينتهي بحالة خروج تساوي 1. 

من أهم مميزات الأمر test تنوع التعابير التي تستطيع كتابتها فيه؛ هذا مثالٌ بسيط:

if [ -f .bash_profile ]; then
    echo "You have a .bash_profile. Things are fine."
else
    echo "Yikes! You have no .bash_profile!"
fi

استخدمنا في المثال السابق التعبير ‎ -f .bash_profile الذي يقول: "هل الملف ‎.bash_profile موجود؟" فإن كان موجودًا فسينتهي الأمر test بحالة خروج تساوي الصفر (أي true) وسيُنفِّذ الأمر if الأوامر التي تتبع الكلمة then؛ وإذا لم يكن الملف موجودًا فسينتهي الأمر test بحالة خروج تساوي الواحد (أي false) وسيُنفِّذ الأمر if الأوامر التي تلي الكلمة else

هذه قائمة مختصرة بالتعابير التي يمكن استعمالها في الأمر test. ولمّا كان الأمر test مُضمَّنًا في الصَدَفة (shell builtin)، فتستطيع استخدام help test لرؤية القائمة الكاملة. 

  • ‎-d dir: الناتج هو true إذا كان dir مجلدًا. 
  • -e file: الناتج هو true إذا كان file موجودًا. 
  • ‎-f file: الناتج هو true إذا كان file ملفًا عاديًا موجودًا. 
  • ‎-L file: الناتج هو true إذا كان file وصلةً رمزيةً (symbolic link). 
  • ‎-r file: الناتج هو true إذا كنت تستطيع قراءة الملف file. 
  • -w file: الناتج هو true إذا كنت تستطيع الكتابة على الملف file. 
  • -x file: الناتج هو true إذا كنت تستطيع تنفيذ الملف file. 
  • file1 -nt file2: الناتج هو true إذا كان الملف file1 أحدث (وفقًا لوقت التعديل [modification time]) من الملف file2. 
  • file1 -ot file2: الناتج هو true إذا كان الملف file1 أقدم من file2. 
  • -z string: الناتج هو true إذا كنت السلسلة النصية string فارغة. 
  • ‎-n string: الناتج هو true إذا لم تكن السلسلة النصية string فارغة. 
  • string1 = string2: الناتج هو true إذا كانت السلسلة النصية string1 مساويةً للسلسلة النصية string2. 
  • string1 != string2: الناتج هو true إذا لم تكن السلسلة النصية string1 مساويةً للسلسلة النصية string2. 

قبل أن نواصل أريد أن أشرح بقية المثال السابق لأنه يبرز عددًا من الأفكار المهمة. 

رأينا في بداية السكربت الأمر if متبوعًا بالأمر test متبوعًا بفاصلة منقوطة وفي النهاية الكلمة then. اخترت استعمال الشكل [ expression ] للأمر test لأنَّ أغلبية الناس يرون أنَّ قراءته أسهل. لاحظ أنَّ الفراغات مطلوبة بين قوس البداية ] وبداية التعبير، وكذلك الفراغ بين نهاية التعبير وقوس الإغلاق [. 

تعمل الفاصلة المنقوطة كفاصل بين الأوامر، مما يسمح بوضع أكثر من أمر في نفس السطر. مثلًا:

$ clear; ls

سيؤدي إلى مسح الشاشة ثم تنفيذ الأمر ls

استخدمتُ الفاصلة المنقوطة في عبارة if السابقة لأنني أريد وضع الكلمة then في نفس سطر الأمر if لأنني أرى أنَّها قراءتها أسهل بهذه الطريقة. 

سنشاهد صديقنا القديم echo في السطر الثاني، لكنك ستلاحظ وجود محاذاة (أي فراغات قبل الأمر)، وهذا لغرض تسهيل قابلية القراءة، ومن التقاليد البرمجية أن نحاذي جميع الأوامر داخل العبارة الشرطية if (أي جميع الأسطر التي ستُنفَّذ عند تحقيق شرط معيّن). تذكَّر أنَّ الصَدَفة لا تتطلَّب فعل ذلك، لكننا نفعله لجعل قراءة الشيفرة أسهل. 

بعبارة أخرى، يمكننا كتابة الشيفرات الآتية وسنحصل على نفس النتيجة:

# Alternate form

if [ -f .bash_profile ]
then
    echo "You have a .bash_profile. Things are fine."
else
    echo "Yikes! You have no .bash_profile!"
fi

# Another alternate form

if [ -f .bash_profile ]
then echo "You have a .bash_profile. Things are fine."
else echo "Yikes! You have no .bash_profile!"
fi

الأمر exit

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

exit 0

سيؤدي إلى إيقاف تنفيذ السكربت وضبط حالة الخروج إلى 0 (أي نجاح التنفيذ)، بينما:

exit 1

سيؤدي إلى إيقاف تنفيذ السكربت وضبط حالة الخروج إلى 1 (أي فشل التنفيذ).

التحقق من أن المستخدم المشغل للسكربت هو الجذر

آخر مرة تركنا فيها السكربت كنا نحتاج إلى تشغيله بامتيازات الجذر، وذلك لأنَّ الدالة home_space تحتاج إلى معرفة المساحة التخزينية التي تستهلكها مجلدات المنزل للمستخدمين. 

لكن ماذا سيحدث لو حاول مستخدمٌ عادي تشغيل السكربت؟ سيؤدي ذلك إلى إظهار الكثير من رسائل الخطأ القبيحة. لكن ماذا لو وضعنا شيئًا في السكربت يمنع تنفيذه من المستخدم العادي؟ 

الأمر id يخبرنا من هو المستخدم الحالي، وعند تمرير الخيار ‎-u إليه فسيطبع قيمة عددية لمُعرِّف المستخدم (user ID) الحالي.

$ id -u
501
$ su
Password:
# id -u
0

فلو نفَّذ المستخدم الجذر الأمر id -u فسيكون الناتج هو 0، ويمكننا أن نستثمر هذه المعلومة لكي تكون الأساس الذي سنبني عليه الاختبار الذي سنضعه في السكربت:

if [ $(id -u) = "0" ]; then
    echo "superuser"
fi

تحققنا في المثال السابق أنَّ ناتج الأمر id -u مساوٍ للسلسلة النصية "0" ثم طبعنا العبارة "superuser". 

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

if [ $(id -u) != "0" ]; then
    echo "You must be the superuser to run this script" >&2
    exit 1
fi

نتحقق في الشيفرة السابقة أنَّ ناتج الأمر id -u لا يساوي "0"، ثم سيطبع السكربت رسالة خطأ مفهومة، ثم ينتهي تنفيذه بحالة خروج مساوية للواحد، لكي يخبر النظام أنَّه لم يتم التنفيذ بنجاح. 

لاحظ وجود ‎>&2 في نهاية الأمر echo، الذي هو شكلٌ من أشكال إعادة توجيه الدخل والخرج (I/O redirection)، وسترى هذا التعبير عادةً في نهاية الأوامر التي تطبع رسائل خطأ، لأننا لو لم نُعِد توجيه مجرى الخطأ هنا، فسترسَل رسالة الخطأ إلى مجرى الخرج القياسي (standard output stream)، لكننا نريد أن تظهر رسائل الخطأ بمعزل عن مخرجات السكربت، لأننا نعيد توجيه مجرى الخرج القياسي الناتج من تنفيذ السكربت إلى ملف. 

علينا أن نضع الأسطر السابقة في بداية السكربت لكي نتمكن من تحديد هوية المستخدم المُشغِّل قبل أن يقع الخطأ، لكننا نريد في الوقت نفسه أنَّ يمكن المستخدمون العاديون من تشغيل السكربت، لذلك سنعدِّل الدالة home_space لكي تتأكد من امتيازات المستخدم كالآتي:

function home_space
{
    # Only the superuser can get this information

    if [ "$(id -u)" = "0" ]; then
        echo "<h2>Home directory space by user</h2>"
        echo "<pre>"
        echo "Bytes Directory"
            du -s /home/* | sort -nr
        echo "</pre>"
    fi

}   # end of home_space

بهذه الطريقة سيتمكن المستخدم العادي من تشغيل السكربت، وسيتم تجاوز الشيفرة التي قد تتسبب بخطأ بدلًا من تنفيذها وإظهار رسالة للمستخدم. 

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



1 شخص أعجب بهذا


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


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



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن