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

بعد أن ازدادت السكربتات التي نكتبها تعقيدًا، فأحببت أن أشير إلى بعض الأخطاء الشائعة التي قد تصادفك أثناء مسيرتك.

shell-scripts-common-mistakes.png

سنتعرض في هذا الدرس مثالًا ونعمل على تحليل الأخطاء التي قد نرتكبها، ولنسمِّ ذاك السكربت trouble.bash، تأكد من كتابة السكربت كما هو موجود حرفيًا.

#!/bin/bash

number=1

if [ $number = "1" ]; then
    echo "Number equals 1"
else
    echo "Number does not equal 1"
fi

يجب أن يُخرِج السكربت السابق السطر "Number equals 1"، لأنَّ المتغير number يساوي 1، إن لم تحصل على المخرجات التي توقعتها، فتحقق من صحة كتابتك للسكربت، فربما ارتكبتَ خطأً…

المتغيرات الفارغة

عدِّل السطر الثالث من السكربت من:

number=1

إلى:

number=

ثم شغِّل السكربت مرةً أخرى، وستحصل هذه المرة على المخرجات الآتية:

$ ./trouble.bash
/trouble.bash: [: =: unary operator expected.
Number does not equal 1

كما لاحظت، عَرَضَت الصَدَفة bash رسالة خطأ عندما شغلنا السكربت، وربما ظننتَ أنَّ حذف القيمة "1" من السطر الثالث قد أدى إلى خطأٍ بنيوي (syntax error) لكن هذا ليس صحيحًا، ألقِ نظرةً إلى رسالة الخطأ مجددًا:

./trouble.bash: [: =: unary operator expected

يمكننا ملاحظة أنَّ ‎./trouble.bash يُبلِّغ عن الخطأ، الذي يرتبط -بشكلٍ أو بآخر- مع ]؛ تذكَّر أنَّ ] هو اختصارٌ للأمر المُضمَّن في الصَدَفة test. ومن رسالة الخطأ السابقة استطعنا تحديد أنَّ الخطأ يحدث في السطر الخامس وليس الثالث. 

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

$ number=
$

أرأيت! لا توجد رسالة خطأ، لكن ما هو الشيء الخاطئ في السطر الخامس؟ لقد جربناه قبل تعديل السكربت ولم يكن يسبب أيّة مشاكل. 

لكي نفهم هذا الخطأ، علينا أن ننظر إلى الموضوع من وجهة نظر الصَدَفة. وَضَعَت الصَدَفة، في السطر الخامس، قيمة المتغير number عندما رأت ‎$number. ففي أول محاولة لتشغيل السكربت (أي عندما كان number=1)، بدَّلَت الصدفة المتغير ‎$number بالرقم 1 كما يلي:

if [ 1 = "1" ]; then

لكننا عندما أزلنا قيمة المتغير (number=‎)، فستحاول الصدفة تنفيذ ما يلي:

if [ = "1" ]; then

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

if [ "$number" = "1" ]; then

وسترى الصَدَفة ما يلي (إذا كانت قيمة number=‎):

if [ "" = "1" ]; then

وبهذا تجنبنا هذا الخطأ. 

تعلمنا من هذا الخطأ أمرًا مهمًا عند كتابة السكربتات: خذ بعين الاعتبار ماذا سيحدث لو كانت قيمة أحد المتغيرات فارغة.

غياب إحدى علامات الاقتباس

عدِّل السطر السادس وأزل علامة إغلاق الاقتباس من نهاية السطر:

echo "Number equals 1

ثم شغِّل السكربت مرةً أخرى وستحصل على:

$ ./trouble.bash
./trouble.bash: line 8: unexpected EOF while looking for matching "
./trouble.bash: line 10 syntax error: unexpected end of file

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

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

عزل مكان المشكلة

قد تكون عملية إيجاد الأخطاء والعِلل في برنامجك صعبة ومحبطة، هذه بعض التقنيات التي قد تستفيد منها: 

اعزل أجزاءً من الشيفرات بوضع علامة تعليق قبل الأسطر التي لا تريد أن تنفذها الصَدَفة. طبِّق هذا على كتلة من الشيفرات لكي ترى إن اختفت مشكلة معيّنة. وبهذه الآلية ستعرف ما هو الجزء الذي يسبب (أو لا يسبب) المشكلة. 

على سبيل المثال، لو عدنا إلى السكربت الذي فيه علامة اقتباس ناقصة، فيمكننا أن نعزل المشكلة كالآتي:

#!/bin/bash

number=1

if [ $number = "1" ]; then
    echo "Number equals 1
#else
#   echo "Number does not equal 1"
fi

سنعلم بعد وضع عبارة else في تعليق ثم تشغيل السكربت أنَّ المشكلة ليس في عبارة else حتى لو أشارت رسالة الخطأ إلى ذلك. 

استخدم الأمر echo للتحقق من القيم. فبعد أن تكتسب خبرة في تتبع العلل، ستكتشف أنَّ العلل ستتواجد في مكانٍ مختلف عن المكان الذي تتوقع وجودها فيه. إحدى المشاكل الشائعة هي افتراضك أنَّ التسلسل المنطقي لبرنامجك صحيحٌ تمامًا، وعندما ترى مشكلة في مرحلة ما في البرنامج فستفترض أنَّها موجودة هناك؛ وهذا خاطئ في معظم الحالات. يمكنك وضع أوامر echo في الشيفرة أثناء محاولة تنقيح (debugging) الأخطاء لكي تحصل على رسائل تؤكد لك أنَّ البرنامج يعمل كما تتوقع. 

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

النوع الثاني يعرض قيم المتغيرات المستخدمة في الحسابات أو الاختبارات. قد تجد في بعض الأحيان قسمًا من السكربت لا يُنفَّذ تنفيذًا سليمًا لأنك افترضت أنَّ شيئًا ما في ما سبقه من الشيفرات صحيحٌ؛ إلا أنَّ فيه مشكلةً تؤدي إلى فشل تنفيذ ما يليه من الشيفرات.

مراقبة تشغيل السكربت

من الممكن أنَّ تعرض لك bash ما الذي يحدث عندما تُشغِّل السكربت. وذلك بإضافة الخيار ‎-x في أول سطر من السكربت كما يلي:

#!/bin/bash -x

وعندما ستُشغِّل السكربت، فستعرض bash كل سطر ستحاول تنفيذه (بعد تعويض قيم المتغيرات…). تُسمى هذه التقنية بالتتبع (tracing)، وهذا مثالٌ عنها:

$ ./trouble.bash
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number equals 1'
Number equals 1

تستطيع أيضًا أن تستعمل الأمر set داخل السكربت لتفعيل أو تعطيل التتبع. استخدم set -x لتشغيل التتبع و set +x لتعطيل التتبع، مثال:

#!/bin/bash

number=1

set -x
if [ $number = "1" ]; then
    echo "Number equals 1"
else
    echo "Number does not equal 1"
fi
set +x

ترجمة -وبتصرّف- للمقال Stay Out Of Trouble لصاحبه 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.


×
×
  • أضف...