صدفة بورن-أغين
معمارية تطبيقات المصادر المفتوحة (المجلد الأول)صدفة Bourne-Again
معمارية تطبيقات المصادر المفتوحة (المجلد الأول) صدفة Bourne-Again
Chet Ramey
إذا استمتعت بهذه الكتب، فقد يعجبك أيضًا التصميم البرمجي بالأمثلة في بايثون (Software Design by Example in Python) والتصميم البرمجي بالأمثلة في جافاسكربت (Software Design by Example in JavaScript).
3.1. المقدمة
توفّر صدفة Unix واجهةً تتيح للمستخدم التفاعل مع نظام التشغيل عبر تنفيذ الأوامر. لكن الصدفة أيضًا لغة برمجة غنية إلى حد كبير: إذ تتوفّر فيها بنى للتحكم في التسلسل، والتبديل بين البدائل، والتكرار، والشروط، والعمليات الحسابية الأساسية، والدوال المسمّاة، ومتغيّرات النصوص، والتواصل ثنائي الاتجاه بين الصدفة والأوامر التي تستدعيها.
يمكن استخدام الأصداف تفاعليًا، من طرفية أو من محاكي طرفية مثل xterm، وغير تفاعليًا بقراءة الأوامر من ملف. وتوفّر معظم الأصداف الحديثة، ومنها bash، تحرير سطر الأوامر، حيث يمكن التلاعب بسطر الأوامر أثناء إدخاله باستخدام أوامر شبيهة بتلك الخاصة بـ emacs أو vi، إضافةً إلى أشكال متنوّعة من محفوظ تاريخ الأوامر.
تشبه معالجة bash إلى حد كبير خط أنابيب داخل الصدفة: فبعد قراءتها من الطرفية أو من سكربت، تمرّ البيانات عبر مراحل عدّة، فتتحوَّل في كل خطوة، حتى تنفّذ الصدفة في النهاية أمرًا وتجمع حالة عودته.
سيستعرض هذا الفصل المكوّنات الرئيسية لـ bash: معالجة المدخلات، والتحليل، وتوسيعات الكلمات المختلفة وسائر عمليات معالجة الأوامر، وتنفيذ الأوامر، من منظور خط الأنابيب. وتعمل هذه المكوّنات بوصفها خط أنابيب للبيانات المقروءة من لوحة المفاتيح أو من ملف، تحوّلها إلى أمرٍ يُنفَّذ.

الشكل 3.1: معمارية مكوّنات bash
3.1.1. bash
bash هي الصدفة التي تظهر في نظام تشغيل GNU، الذي يُبنى عادةً فوق نواة لينكس، وفي عدد من أنظمة التشغيل الشائعة الأخرى، وأبرزها Mac OS X. وهي تقدّم تحسينات وظيفية على الإصدارات التاريخية من sh، سواء في الاستخدام التفاعلي أو في البرمجة.
الاسم اختصار لعبارة Bourne-Again SHell، وهي مزحة لغوية تجمع بين اسم Stephen Bourne (مؤلف السلف المباشر للصدفة الحالية في يونكس /bin/sh، التي ظهرت في نسخة Bell Labs Seventh Edition من يونكس البحثي) وبين فكرة الولادة من جديد عبر إعادة التنفيذ. وكان المؤلف الأصلي لـ bash هو Brian Fox، وهو موظف في مؤسسة Free Software Foundation. وأنا المطوّر والصيانة الحالي، وأنا متطوّع يعمل في جامعة Case Western Reserve في كليفلاند بولاية أوهايو.
ومثل بقية برمجيات GNU، فإن bash قابل للنقل إلى حد كبير. فهو يعمل اليوم على كل إصدارات يونكس تقريبًا وعلى بضعة أنظمة تشغيل أخرى — إذ توجد منفذات مدعومة بشكل مستقل لبيئات ويندوز المستضافة مثل Cygwin وMinGW، كما أن المنفذات إلى الأنظمة الشبيهة بيونكس مثل QNX وMinix جزء من حزمة التوزيع. ولا يتطلّب الأمر سوى بيئة Posix لبنائه وتشغيله، مثل تلك التي توفّرها Services for Unix (SFU) من مايكروسوفت.
3.2. الوحدات النحوية والأوّليات
3.2.1. الأوّليات
بالنسبة إلى bash، هناك ثلاثة أنواع أساسية من الرموز: الكلمات المحجوزة، والكلمات، والرموز التشغيلية (operators). والكلمات المحجوزة هي التي لها معنى بالنسبة إلى الصدفة وإلى لغة برمجتها؛ وعادةً ما تُدخل هذه الكلمات بنى التحكم في التسلسل، مثل if وwhile. أمّا الرموز التشغيلية فتتألف من محرف خام أو أكثر: محارف لها معنى خاص بالصدفة بذاتها، مثل | و>. ويتكوّن بقية مدخلات الصدفة من كلمات عادية، بعضها ذو معنى خاص — مثل جمل الإسناد أو الأرقام — تبعًا لموضع ظهوره على سطر الأوامر.
3.2.2. المتغيّرات والمعاملات
كما في أي لغة برمجة، توفّر الأصدفة متغيّرات: أسماءٌ للإشارة إلى بيانات مخزّنة والتعامل معها. وتوفّر الصدفة متغيّرات أساسية يستطيع المستخدم ضبطها، إضافةً إلى بعض المتغيّرات المدمجة التي يُشار إليها بالمعاملات (parameters). ومعظم معاملات الصدفة تعكس جانبًا من حالتها الداخلية، وتُضبط تلقائيًا أو نتيجةً جانبية لعمل آخر.
قيم المتغيّرات نصوص. وتُعامَل بعض القيم بصورة خاصة تبعًا للسياق، وسنشرح ذلك لاحقًا. وتُسنَد المتغيّرات بجمل من الشكل name=value. والـ value اختياري؛ فإغفالها يُسند السلسلة الفارغة إلى name. وإذا قُدّمت القيمة، وسّعتها الصدفة ثم أسندتها إلى name. وتستطيع الصدفة تنفيذ عمليات مختلفة تبعًا لما إذا كان المتغيّر مضبوطًا أم لا، لكن إسناد قيمة هو الطريقة الوحيدة لضبط متغيّر. أمّا المتغيّرات التي لم تُسنَد إليها قيمة، حتى إن كانت معلَنة ومُنحت سمات، فيُشار إليها بأنها غير مضبوطة (unset).
الكلمة التي تبدأ بعلامة الدولار تُدخل إشارة إلى متغيّر أو معامل. وتُستبدل الكلمة، بما فيها علامة الدولار، بقيمة المتغيّر المسمّى. وتوفّر الصدفة مجموعةً غنية من رموز التوسيع، من الاستبدال البسيط بالقيمة إلى تغيير أجزاء من قيمة المتغيّر أو إزالتها عندما تطابق نمطًا.
هناك توفّر للمتغيّرات المحلية والعالمية. وافتراضيًا، جميع المتغيّرات عالمية. ويمكن تقديم أي أمر بسيط (أكثر أنواع الأوامر ألفًا — اسم أمر ومجموعة اختيارية من الوسائط وإعادة التوجيه) بجمل إسناد تسبقه، فتوجد تلك المتغيّرات لهذا الأمر وحده. وتطبّق الصدفة إجراءات محفوظة، أو ما يُسمّى بدوال الصدفة (shell functions)، ويمكن أن يكون لها متغيّرات خاصة بالدالة.
يمكن أن تكون للمتغيّرات أنواع محدودة إلى حد كبير: فإلى جانب المتغيّرات البسيطة ذات القيم النصية، هناك الأعداد الصحيحة والمصفوفات. وتُعامَل المتغيّرات من النوع الصحيح كأعداد: إذ يُوسَّع أي نص يُسنَد إليها كنتعبير حسابي، ثم تُسنَد النتيجة قيمةً للمتغيّر. وقد تكون المصفوفات مفهرسة أو ترابطية؛ إذ تستخدم المصفوفات المفهرسة الأرقام كفهارس فرعية (subscripts)، بينما تستخدم المصفوفات الترابطية نصوصًا عشوائية. وعناصر المصفوفة نصوص، ويمكن معاملتها كأعداد صحيحة عند الرغبة. ولا يجوز أن تكون عناصر المصفوفة مصفوفات أخرى.
يستخدم bash جداول التجزئة (hash tables) لتخزين متغيّرات الصدفة واسترجاعها، وقوائم مترابطة من هذه الجداول لتنفيذ نطاق المتغيّرات (variable scoping). وهناك نطاقات متغيّرات مختلفة لاستدعاءات دوال الصدفة، ونطاقات مؤقتة للمتغيّرات التي تضبطها جمل الإسناد السابقة لأمر ما. وحين تسبق جمل الإسناد تلك أمرًا مدمجًا في الصدفة، على سبيل المثال، يتعيّن على الصدفة تتبّع الترتيب الصحيح لحل مراجع المتغيّرات، وتتيح لها النطاقات المترابطة ذلك. وقد يكون عدد النطاقات التي يلزم اجتيازها مفاجئًا، تبعًا لمستوى التعشيش في التنفيذ.
3.2.3. لغة برمجة الصدفة
يتكوّن أمر الصدفة البسيط — وهو الأمر الأكثر ألفًا لدى معظم القرّاء — من اسم أمر، مثل echo أو cd، ومن قائمة من صفر أو أكثر من الوسائط وإعادة التوجيه. وتتيح إعادة التوجيه لمستخدم الصدفة التحكم في مدخلات الأوامر المستدعاة ومخرجاتها. وكما ذكرنا أعلاه، يستطيع المستخدمون تعريف متغيّرات محلية لأوامر بسيطة.
تقدّم الكلمات المحجوزة أوامر صدفة أكثر تعقيدًا. وهناك بنى مشتركة مع أي لغة برمجة عالية المستوى، مثل if-then-else وwhile، وحلقة for التي تتكرّر على قائمة قيم، وحلقة for حسابية شبيهة بلغة C. وتتيح هذه الأوامر الأكثر تعقيدًا للصدفة تنفيذ أمر أو اختبار شرط بغير ذلك، وإجراء عمليات مختلفة تبعًا للنتيجة، أو تنفيذ الأوامر عدّة مرّات.
من بين الهدايا التي قدّمها Unix لعالم الحوسبة خط الأنابيب (pipeline): قائمة خطّية من الأوامر، يصبح فيها خرج أحد الأوامر في القائمة مدخلًا للأمر التالي. ويمكن استخدام أي بنية في الصدفة ضمن خط أنابيب، ومن غير النادر رؤية خطوط أنابيب يمرّر فيها أمرٌ البيانات إلى حلقة.
يطبّق bash آلية تتيح إعادة توجيه تيارات الإدخال القياسي والإخراج القياسي والخطأ القياسي لأمر ما إلى ملف أو عملية أخرى عند استدعائه. ويستطيع مبرمجو الأصدفة كذلك استخدام إعادة التوجيه لفتح الملفات وإغلاقها في بيئة الصدفة الحالية.
يسمح bash بتخزين برامج الأصدفة واستخدامها أكثر من مرّة. ودوال الصدفة وسكربتات الصدفة هما وسيلتان لتسمية مجموعة من الأوامر وتنفيذها، تمامًا كما يُنفَّذ أي أمر آخر. وتُعلَن دوال الصدفة بصيغة خاصة، وتُخزَّن وتُنفَّذ في سياق الصدفة نفسها؛ أمّا سكربتات الصدفة فتُنشأ بوضع الأوامر في ملف وتشغيل نسخة جديدة من الصدفة لتفسيره. وتشارك دوال الصدفة معظم سياق التنفيذ مع الصدفة التي تستدعيها، أمّا سكربتات الصدفة فبما تُفسَّر باستدعاء جديد للصدفة، فلا تشارك سوى ما يُمرَّر بين العمليات في البيئة.
3.2.4. ملاحظة إضافية
أثناء قراءتك لما يلي، ضع في اعتبارك أن الصدفة تنفّذ ميزاتها باستخدام عدد قليل من بنى البيانات فقط: المصفوفات، والأشجار، والقوائم المترابطة أحادية الاتجاه وثنائية الاتجاه، وجداول التجزئة. وتقريبًا كل بنى الصدفة منفَّذة باستخدام هذه الأوّليات.
بنية البيانات الأساسية التي تستخدمها الصدفة لتمرير المعلومات من مرحلة إلى التي تليها، وللتعامل مع وحدات البيانات داخل كل مرحلة معالجة، هي WORD_DESC:
typedef struct word_desc {
char *word; /* Zero terminated string. */
int flags; /* Flags associated with this word. */
} WORD_DESC;
وتُدمج الكلمات في قوائم وسائط مثلًا، باستخدام قوائم مترابطة بسيطة:
typedef struct word_list {
struct word_list *next;
WORD_DESC *word;
} WORD_LIST;
إنّ WORD_LISTs منتشرة في أرجاء الصدفة. فالأمر البسيط قائمة كلمات، ونتيجة التوسيع قائمة كلمات، وكل أمر مدمج يأخذ قائمة كلمات من الوسائط.
3.3. معالجة المدخلات
المرحلة الأولى في خط معالجة bash هي معالجة المدخلات: أخذ المحارف من الطرفية أو من ملف، وتقسيمها إلى أسطر، وتمرير الأسطر إلى محلل الصدفة ليحوّلها إلى أوامر. وكما يتوقّع المرء، تكون الأسطر تسلسلات من المحارف تنتهي بأسطر جديدة.
3.3.1. Readline وتحرير سطر الأوامر
تقرأ bash المدخلات من الطرفية عند التشغيل التفاعلي، ومن ملف السكربت المحدَّد كوسيط في حالة غير ذلك. وعند التفاعلية، تسمح bash للمستخدم بتحرير أسطر الأوامر أثناء كتابتها، باستخدام تسلسلات مفاتيح مألوفة وأوامر تحرير شبيهة بتلك الموجودة في محرّري Unix emacs وvi.
تستخدم bash مكتبة readline لتحرير سطر الأوامر. وتوفّر هذه المكتبة مجموعة دوال تتيح للمستخدمين تحرير أسطر الأوامر، ودوالًا لحفظ أسطر الأوامر أثناء إدخالها، ولاستدعاء الأوامر السابقة، ولتنفيذ توسيع تاريخ شبيه بـ csh. وbash هو العميل الرئيسي لـ readline، ويُطوَّران معًا، لكن لا توجد في readline شيفرة خاصة بـ bash. وقد تبنّى كثير من المشاريع الأخرى readline لتوفير واجهة تحرير أسطر طرفية.
تتيح readline للمستخدمين أيضًا ربط تسلسلات مفاتيح بطول غير محدود بأيٍّ من العدد الكبير من أوامر readline. وتتوفّر في readline أوامر لتحريك المؤشّر على طول السطر، وإدراج النص وحذفه، واسترجاع أسطر سابقة، وإكمال الكلمات المُدخلة جزئيًا. وفوق ذلك، يمكن للمستخدمين تعريف ماكروهات (macros)، وهي نصوص من المحارف تُدرَج في السطر استجابةً لتسلسل مفاتيح، باستخدام الصيغة نفسها المستخدمة في ربط المفاتيح. وتوفّر الماكروهات لمستخدمي readline وسيلة بسيطة للاستبدال النصي والاختصار.
بنية Readline
بُنيت readline كحلقة أساسية من القراءة/التوجيه/التنفيذ/إعادة العرض. فهي تقرأ المحارف من لوحة المفاتيح باستخدام read أو ما يماثله، أو تحصل على المدخلات من ماكرو. ويُستخدم كل محرف كفهرس في خريطة مفاتيح (keymap)، أو جدول توجيه. ومع أن الفهرسة تتم بمحرف واحد من ثمانية بتات، فإن محتوى كل عنصر في خريطة المفاتيح قد يكون عدة أمور. فقد تحلّ المحارف إلى خرائط مفاتيح إضافية، وهكذا تصير تسلسلات المفاتيح متعددة المحارف ممكنة. أمّا إن حُلّ المحرف إلى أمر من أوامر readline، مثل beginning-of-line، فيُنفَّذ ذلك الأمر. ويُخزَّن المحرف المرتبط بأمر self-insert في مخزن التحرير. ومن الممكن أيضًا ربط تسلسل مفاتيح بأمر، مع ربط توابعه الفرعية في الوقت نفسه بأوامر مختلفة (وهي ميزة أُضيفت حديثًا نسبيًا)؛ وهناك فهرس خاص في خريطة المفاتيح يشير إلى حدوث ذلك. وربط تسلسل مفاتيح بماكرو يمنح مرونة كبيرة، من إدراج نصوص عشوائية في سطر الأوامر إلى إنشاء اختصارات لوحة مفاتيح لتسلسلات تحرير معقّدة. وتخزّن readline كل محرف مرتبط بـ self-insert في مخزن التحرير، وهو مخزن قد يشغل عند عرضه سطرًا واحدًا أو أكثر على الشاشة.
تدير readline مخازن المحارف والسلاسل فقط باستخدام char من لغة C، وتبني فوقها المحارف متعددة البايت عند الحاجة. وهي لا تستخدم wchar_t داخليًا، وذلك لأسباب تتعلق بالسرعة والتخزين، ولأن شيفرة التحرير كانت موجودة قبل أن يصبح دعم المحارف متعددة البايت شائعًا. وفي الإعدادات المحلية (locale) التي تدعم المحارف متعددة البايت، تقرأ readline محرفًا متعدد البايت كاملًا وتدرجه في مخزن التحرير. ومن الممكن ربط المحارف متعددة البايت بأوامر التحرير، لكن يلزم ربط مثل هذا المحرف كتسلسل مفاتيح؛ وهذا ممكن لكنه صعب وغير مرغوب فيه عادةً. فمجموعتا أوامر emacs وvi القائمتان لا تستخدمان محارف متعددة البايت، على سبيل المثال.
ومتى ما انحلّ تسلسل المفاتيح في النهاية إلى أمر تحرير، تحدّث readline عرض الطرفية ليعكس النتائج. ويحدث ذلك سواء نتج عن الأمر إدراج محارف في المخزن، أم نقل موضع التحرير، أم استبدال السطر كليًا أو جزئيًا. وبعض أوامر التحرير القابلة للربط، مثل تلك التي تعدّل ملف التاريخ، لا تسبّب أي تغيير في محتويات مخزن التحرير.
تحديث عرض الطرفية، رغم بساطته الظاهرية، معقّد إلى حد كبير. إذ يتعيّن على readline تتبّع ثلاثة أمور: المحتويات الحالية لمخزن المحارف المعروض على الشاشة، والمحتويات المحدَّثة لذلك المخزن، والمحارف المعروضة فعليًا. وفي وجود المحارف متعددة البايت، لا تطابق المحارف المعروضة المخزن تمامًا، ويجب أن يراعي محرك إعادة العرض ذلك. وعند إعادة العرض، تقارن readline محتويات مخزن العرض الحالي بالمخزن المحدَّث، وتستنتج الفروق، وتقرّر كيف تُعدّل العرض بأعلى كفاءة ليعكس المخزن المحدَّث. وقد كانت هذه المشكلة موضوع بحث كبير على مدى السنين (وهي مسألة التصحيح من نص إلى نص). وأسلوب readline هو تحديد بداية الجزء المخالف من المخزن ونهايته، وحساب كلفة تحديث ذلك الجزء وحده، بما في ذلك تحريك المؤشّر إلى الخلف ثم إلى الأمام (مثلًا: هل يكلّف إصدار أوامر طرفية لحذف محارف ثم إدراج محارف جديدة أكثر من الكتابة فوق محتوى الشاشة الحالي؟)، ثم تنفيذ التحديث الأقل كلفة، ثم التنظيف بإزالة أي محارف متبقية في نهاية السطر عند الحاجة ووضع المؤشّر في الموضع الصحيح.
محرك إعادة العرض هو بلا شكّ الجزء الذي تعرّض لأكبر قدر من التعديل في readline. وكانت معظم التغييرات قد أُضيفت لزيادة الوظائف — وأهمّها القدرة على وجود محارف لا تُعرض في سلسلة التعليمات (لتغيير الألوان مثلًا)، والتعامل مع المحارف التي تشغل أكثر من بايت واحد.
تعيد readline محتويات مخزن التحرير إلى التطبيق المستدعي، الذي يكون مسؤولًا بدوره عن حفظ النتائج المحتملة التعديل في قائمة التاريخ.
التطبيقات التي توسّع Readline
تمامًا كما تتيح readline للمستخدمين طرقًا متنوّعة لتخصيص سلوكها الافتراضي وتوسيعه، فهي توفّر عددًا من الآليات التي تمكّن التطبيقات من توسيع مجموعتها الافتراضية من الميزات. أولًا، تقبل دوال readline القابلة للربط مجموعةً معيارية من الوسائط وتعيد مجموعةً محدَّدة من النتائج، مما يجعل من السهل على التطبيقات أن توسّع readline بدوال خاصة بها. فbash مثلًا يضيف أكثر من ثلاثين أمرًا قابلًا للربط، من إكمالات الكلمات الخاصة بـ bash إلى واجهات لأوامر الصدفة المدمجة.
أمّا الطريقة الثانية التي تتيح بها readline للتطبيقات تعديل سلوكها فتتم عبر الاستخدام الواسع للمؤشّرات لربط دوال ذات أسماء وواجهات استدعاء معروفة. فيمكن للتطبيقات استبدال بعض أجزاء البنية الداخلية لـ readline، ووضع وظائف اعتراضية أمامها، وإجراء تحويلات خاصة بالتطبيق.
3.3.2. معالجة المدخلات غير التفاعلية
حين لا تستخدم الصدفة readline، فإنها تستخدم إمّا stdio أو إجراءات الإدخال المخزَّنة الخاصة بها للحصول على المدخلات. وتُفضَّل حزمة الإدخال المخزَّنة في bash على stdio حين لا تكون الصدفة تفاعلية، بسبب القيود الشاذّة نسبيًا التي يفرضها Posix على استهلاك المدخلات: يجب على الصدفة أن تستهلك فقط المدخلات اللازمة لتحليل أمر، وتترك الباقي للبرامج المنفَّذة. وهذا مهمّ بصفة خاصة حين تقرأ الصدفة سكربتًا من الإدخال القياسي. ويُسمح للصدفة بتخزين المدخلات بقدر ما تشاء، ما دامت قادرة على إرجاع إزاحة الملف إلى ما بعد آخر محرف يستهلكه المحلل مباشرة. ومن الناحية العملية، يعني هذا أن على الصدفة قراءة السكربتات محرفًا محرفًا عند القراءة من أجهزة غير قابلة للتمرير مثل الأنابيب، لكن بوسعها تخزين قدر ما تشاء من المحارف عند القراءة من الملفات.
ودون هذه السمات الخاصة، فإن مخرجات جزء المدخلات غير التفاعلية من معالجة الصدفة هي نفسها التي تنتجها readline: مخزن من المحارف ينتهي بسطر جديد.
3.3.3. المحارف متعددة البايت
أُضيفت معالجة المحارف متعددة البايت إلى الصدفة بعد زمن طويل من تنفيذها الأولي، وقد روعي في ذلك طريقةٌ مصمَّمة للحدّ من أثرها على الشيفرة القائمة. وفي الإعدادات المحلية التي تدعم المحارف متعددة البايت، تخزّن الصدفة مدخلاتها في مخزن من البايتات (char من C)، لكنّها تتعامل مع هذه البايتات بوصفها محارف محتملة متعددة البايت. وتفهم readline كيفية عرض المحارف متعددة البايت (المفتاح هو معرفة عدد مواضع الشاشة التي يشغلها المحرف متعدد البايت، وعدد البايتات التي ينبغي استهلاكها من المخزن عند عرض محرف على الشاشة)، وكيفية التنقّل إلى الأمام وإلى الخلف في السطر محرفًا محرفًا بدلًا من بايتًا بايت، وهكذا. وإلى جانب ذلك، ليس للمحارف متعددة البايت أثرٌ كبير على معالجة مدخلات الصدفة. أمّا الأجزاء الأخرى من الصدفة، الموصوفة لاحقًا، فتحتاج إلى أن تكون على علم بالمحارف متعددة البايت وأن تأخذها في الحسبان عند معالجة مدخلاتها.
3.4. التحليل النحوي
المهمة الأولى لمحرك التحليل هي التحليل اللغوي (lexical analysis): فصل تدفّق المحارف إلى كلمات وإسناد معنى إلى الناتج. والكلمة هي الوحدة الأساسية التي يعمل عليها المحلل. والكلمات تسلسلات من المحارف تفصل بينها محارف خام، منها الفواصل البسيطة مثل المسافات وعلامات التبويب، والمحارف الخاصة بلغة الصدفة مثل الفواصل المنقوطة وعلامات &.
تتمثّل إحدى المشكلات التاريخية للصدفة، كما قال Tom Duff في ورقته البحثية عن rc، صدفة Plan 9، في أن لا أحد يعرف حقًا ما هي قواعد لغة صدفة Bourne. وتستحق لجنة Posix للصدفات تقديرًا كبيرًا لنشرها قواعدًا نحوية نهائية لصدفة Unix، وإن كانت تحمل قدرًا كبيرًا من التبعيات السياقية. وهي قواعد لا تخلو من مشكلاتها — فهي تحظر بعض البنى التي قبلتها محلّلات صدفة Bourne التاريخية من دون خطأ — لكنها خير ما لدينا.
ومشتقّ محلّل bash من نسخة مبكرة من قواعد Posix، وهو، على حدّ علمي، المحلّل الوحيد بصيغة صدفة Bourne منفَّذ باستخدام Yacc أو Bison. وقد سبّبت هذه المسألة مشكلات خاصة بها — فقواعد الصدفة ليست في الحقيقة مناسبة للتحليل بأسلوب yacc، وتتطلّب تحليلًا لغويًا معقّدًا قدرًا كبيرًا من التعاون بين المحلل النحوي والمحلل اللغوي.
على أي حال، يأخذ المحلل اللغوي أسطر المدخلات من readline أو من مصدر آخر، ويقسّمها إلى رموز عند المحارف الخام، ويحدّد هوية كل رمز بحسب السياق، ثم يمرّرها إلى المحلل النحوي لتُجمَع في جمل وأوامر. وهناك قدر كبير من السياق الضروري — فمثلًا، قد تكون الكلمة for كلمة محجوزة، أو مُعرِّفًا، أو جزءًا من جملة إسناد، أو كلمة أخرى، والأمر التالي صالح تمامًا:
for for in for; do for=for; done; echo $for
الذي يطبع for.
هنا يحسن بنا أن ننتقل قليلًا إلى الحديث عن الأوامر المستعارة (aliasing). تسمح bash باستبدال الكلمة الأولى من أمر بسيط بنص عشوائي باستخدام الأوامر المستعارة. ولأنها معجمية محضة، يمكن حتى استخدام الأسماء المستعارة (أو إساءة استخدامها) لتغيير قواعد لغة الصدفة: فمن الممكن كتابة اسم مستعار ينفّذ أمرًا مركبًا لا توفّره bash. وينفّذ محلل bash الأسماء المستعارة بالكامل في الطور اللغوي، مع أن على المحلل النحوي أن يُبلغ المحلل اللغوي متى يُسمح بتوسيع الأسماء المستعارة.
وكما في لغات برمجة كثيرة، تسمح الصدفة بهروب المحارف لإزالة معناها الخاص، بحيث يمكن أن تظهر محارف خام مثل & داخل الأوامر. وهناك ثلاثة أنواع من الاقتباس، يختلف كل منها قليلًا ويسمح بتفسيرات مختلفة قليلًا للنص المُقتبس: الشرطة المائلة العكسية، وهي تهرّب المحرف التالي؛ وعلامات الاقتباس المفردة، وهي تمنع تفسير كل المحارف المحيطة بها؛ وعلامات الاقتباس المزدوجة، وهي تمنع بعض التفسيرات لكنها تسمح ببعض توسيعات الكلمات (وتتعامل مع الشرطات المائلة العكسية على نحو مختلف). ويفسّر المحلل اللغوي المحارف والسلاسل المقتبسة، ويمنع التعرّف عليها من المحلل النحوي بوصفها كلمات محجوزة أو محارف خام. وهناك أيضًا حالتان خاصتان، $'…' و$"…"، توسّعان المحارف المهروبة بشرطة مائلة عكسية بالطريقة نفسها التي تتوسّع بها سلاسل ANSI C، وتتيحان ترجمة المحارف باستخدام دوال التدويل القياسية، على التوالي. والأولى مستخدَمة على نطاق واسع، أمّا الثانية فاستخدامها أقل،ربما لقلة الأمثلة الجيدة أو حالات الاستخدام.
بقية الواجهة بين المحلل النحوي والمحلل اللغوي مباشرة. إذ يُرمّز المحلل النحوي قدرًا معيّنًا من الحالة ويشاركه مع المحلل اللغوي بما يتيح نوع التحليل المعتمد على السياق الذي تتطلبه القواعد. فمثلًا، يصنّف المحلل اللغوي الكلمات بحسب نوع الرمز: كلمة محجوزة (في السياق المناسب)، أو كلمة، أو جملة إسناد، وهكذا. ولكي يفعل ذلك، لا بدّ أن يخبره المحلل النحوي بمقدار ما أنجزه من تحليل أمر، وبما إذا كان يعالج سلسلة نصية متعدّدة الأسطر (ما تُسمّى أحيانًا «مستند هنا» (here-document))، أم أنه داخل جملة case أو أمر شرطي، أم أنه يعالج نمط صدفة موسّعًا أو جملة إسناد مركّبة.
أُجريت معظم أعمال التعرّف على نهاية استبدال الأمر في مرحلة التحليل داخل دالة واحدة (parse_comsub)، وهي دالة تعرف قدرًا غير مريح من صياغة الصدفة وتكرّر من شيفرة قراءة الرموز أكثر مما ينبغي. إذ يتعيّن عليها أن تعرف عن مستندات هنا، وتعليقات الصدفة، والمحارف الخام وحدود الكلمات، والاقتباس، ومتى تكون الكلمات المحجوزة مقبولة (حتى تعرف أنها داخل جملة case)؛ وقد استغرق ضبط ذلك بعض الوقت.
وعند توسيع استبدال أمر أثناء توسيع الكلمات، يستخدم bash المحلل النحوي للعثور على النهاية الصحيحة للبنية. وهذا يشبه تحويل نص إلى أمر لأجل eval، لكن الأمر هنا لا يُنهيه نهاية النص. ولجعل ذلك ممكنًا، يجب أن يتعرّف المحلل النحوي على قوس الإغلاق بوصفه نهايةً صالحة للأمر، ما يؤدّي إلى حالات خاصة في عدد كبير من إنتاجات القواعد، ويستلزم من المحلل اللغوي أن يضع علامة على قوس الإغلاق (في السياق المناسب) على أنه يدلّ على EOF. وعلى المحلل النحوي أيضًا أن يحفظ حالة التحليل النحوي ويستعيدها قبل استدعاء yyparse استدعاءً تعاوديًا، إذ يمكن تحليل استبدال أمر وتنفيذه كجزء من توسيع سلسلة تعليمات في منتصف قراءة أمر. ولأن دوال المدخلات تنفّذ القراءة الاستباقية، يتعيّن على هذه الدالة في النهاية أن تعيد مؤشر إدخال bash إلى الموضع الصحيح، سواء كانت bash تقرأ المدخلات من نص أو من ملف أو من الطرفية باستخدام readline. وهذا مهمّ ليس فقط تفاديًا لفقدان المدخلات، بل أيضًا كي تبني دوال توسيع استبدال الأمر النص الصحيح للتنفيذ.
والإكمال القابل للبرمجة للكلمات يطرح مشكلات مماثلة، إذ يسمح بتنفيذ أوامر عشوائية أثناء تحليل أمر آخر، وتُحلّ هذه المشكلات بحفظ حالة المحلل النحوي واستعادتها حول الاستدعاءات.
والاقتباس مصدرٌ آخر للتعارض والجدل. فبعد عشرين سنة من نشر أول معيار Posix للصدفة، لا يزال أعضاء فريق عمل المعايير يناقشون السلوك الصحيح للاقتباس النادر الاستخدام. وكما سبق، لا تساعد صدفة Bourne في شيء، إلا بوصفها مرجعًا تنفيذيًّا يمكن ملاحظة السلوك من خلاله.
ويعيد المحلل النحوي بنية C واحدة تمثّل أمرًا (قد تتضمّن هذه البنية، في حالة الأوامر المركبة كالحلقات، أوامر أخرى بدورها)، ويمرّرها إلى المرحلة التالية من عمل الصدفة: توسيع الكلمات. وتتألف بنية الأمر من كائنات أوامر وقوائم كلمات. ومعظم قوائم الكلمات تخضع لتحويلات متنوّعة تبعًا لبيئتها، كما هو موضّح في الأقسام التالية.
3.5. توسيع الكلمات
بعد التحليل، ولكن قبل التنفيذ، تتعرّض كثير من الكلمات التي ينتجها طور التحليل لتوسيع واحد أو أكثر من توسيعات الكلمات، بحيث يُستبدل $OSTYPE مثلًا بالنص "linux-gnu".
3.5.1. توسيع المعاملات والمتغيّرات
توسيعات المتغيّرات هي التي يجدها المستخدمون أكثر ألفًا. فمتغيّرات الصدفة ضعيفة الأنماط، وتُعامَل — باستثناءات قليلة — بوصفها نصوصًا. وتوسّع هذه التوسيعات تلك النصوص وتحوّلها إلى كلمات وقوائم كلمات جديدة.
وهناك توسيعات تقع على قيمة المتغيّر نفسه. إذ يستطيع المبرمجون استخدامها لإنتاج أجزاء فرعية من قيمة متغيّر، أو طوله، أو لإزالة أجزاء تطابق نمطًا محدَّدًا من بدايتها أو نهايتها، أو لاستبدال أجزاء من القيمة تطابق نمطًا محدَّدًا بنص جديد، أو لتغيير حالة الأحرف الأبجدية في قيمة المتغيّر.
إلى جانب ذلك، هناك توسيعات تعتمد على حالة المتغيّر: فتحدث توسيعات أو إسنادات مختلفة تبعًا لما إذا كان المتغيّر مضبوطًا أم لا. فمثلًا، سيتوسّع ${parameter:-word} إلى parameter إن كان مضبوطًا، وإلى word إن لم يكن مضبوطًا أو كان مضبوطًا على السلسلة الفارغة.
3.5.2. وغيرها الكثير
يقوم bash بأنواع كثيرة أخرى من التوسيع، لكل منها قواعده الخاصة الغريبة. والأول في ترتيب المعالجة هو توسيع الأقواس المعقوفة، الذي يحوّل:
pre{one,two,three}post
إلى:
preonepost pretwopost prethreepost
وهناك أيضًا استبدال الأوامر، وهو زواج مريح بين قدرة الصدفة على تنفيذ الأوامر والتعامل مع المتغيّرات. فتنفّذ الصدفة أمرًا، وتجمع مخرجاته، ثم تستخدم هذه المخرجات قيمةً للتوسيع.
ومن مشكلات استبدال الأوامر أنه ينفّذ الأمر المُحاط فورًا وينتظر اكتماله: فلا توجد طريقة سهلة تجعل الصدفة ترسل إليه مدخلات. ويستخدم bash ميزة تُسمّى استبدال العمليات (process substitution)، وهي نوع من المزيج بين استبدال الأوامر وخطوط أنابيب الصدفة، للتعويض عن هذه العيوب. وكما في استبدال الأوامر، ينفّذ bash أمرًا، لكنه يتركه يعمل في الخلفية ولا ينتظر اكتماله. والمفتاح هنا أن bash يفتح أنبوبًا نحو الأمر للقراءة أو الكتابة، ويعرضه باسم ملف، فيصبح اسم الملف هذا هو نتيجة التوسيع.
يلي ذلك توسيع الشرطة المائلة العكسية. فقد كان مقصودًا في الأصل تحويل ~alan إلى إشارة إلى دليل المنزل الخاص بـ Alan، لكنه نما على مدى السنين ليصبح وسيلة للإشارة إلى عدد كبير من المجلدات المختلفة.
وأخيرًا، هناك التوسيع الحسابي. فـ $((expression)) يجعل expression يُقيَّم وفق القواعد نفسها المطبَّقة على تعبيرات لغة C. ويصبح نتيجة التعبير هي نتيجة التوسيع.
توسيع المتغيّرات هو الموضع الذي يصبح فيه الفرق بين علامتي الاقتباس المفرد والمزدوج أكثر ما يكون وضوحًا. فالاقتباس المفرد يمنع جميع التوسيعات — فتمرّ المحارف المحيطة بالعلامات من خلال التوسيعات بلا مساس — فيما يسمح الاقتباس المزدوج ببعض التوسيعات ويمنع أخرى. فتحدث توسيعات الكلمات واستبدال الأوامر والحساب واستبدال العمليات — إذ لا يؤثر الاقتباس المزدوج إلا في كيفية التعامل مع النتيجة — بينما لا تحدث توسيعات الأقواس المعقوفة وتوسيع الشرطة المائلة العكسية.
3.5.3. تقسيم الكلمات
تُقسَّم نتائج توسيع الكلمات باستخدام المحارف الواردة في قيمة متغيّر الصدفة IFS بوصفها محدِّدات. وهكذا تحوّل الصدفة كلمة واحدة إلى أكثر من واحدة. وكلما ظهر أحد المحارف في $IFS[1] ضمن النتيجة، يقسّم bash الكلمة إلى اثنتين. ويمنع الاقتباس المفرد والاقتباس المزدوج كلاهما تقسيم الكلمات.
3.5.4. التوسيع الشاهي (Globbing)
بعد تقسيم النتائج، تفسّر الصدفة كل كلمة ناتجة عن التوسيعات السابقة بوصفها نمطًا محتملًا، وتحاول مطابقته مع اسم ملف قائم، بما في ذلك أي مسار مجلد سابق له.
3.5.5. التنفيذ
إذا كانت البنية الأساسية للصدفة تحاكي خط أنابيب، فإن توسيعات الكلمات خط أنابيب صغير بحد ذاته. فكل مرحلة من مراحل توسيع الكلمات تأخذ كلمة، وتحوّلها بعد ذلك، ثم تمرّرها إلى مرحلة التوسيع التالية. وبعد إتمام جميع توسيعات الكلمات، يُنفَّذ الأمر.
يبني تنفيذ bash لتوسيع الكلمات على بنى البيانات الأساسية الموصوفة سابقًا. فالكلمات التي يخرج بها المحلل النحوي تُوسَّع فرادى، فتنتج كلمة واحدة أو أكثر لكل كلمة إدخال. وقد أثبتت بنية البيانات WORD_DESC مرونة كافية للاحتفاظ بجميع المعلومات اللازمة لتغليف توسيع كلمة واحدة. وتُستخدم الأعلام (flags) لترميز معلومات يستعملها طور توسيع الكلمات، ولتمرير المعلومات من مرحلة إلى التي تليها. فمثلًا، يستخدم المحلل النحوي علمًا ليخبر مرحلتي التوسيع وتنفيذ الأمر بأن كلمة معيّنة هي جملة إسناد في الصدفة، وتستخدم شيفرة توسيع الكلمات الأعلام داخليًا لمنع تقسيم الكلمات أو للإشارة إلى وجود نص فارغ مقتبس ("$x"، حيث يكون $x غير مضبوط أو ذا قيمة فارغة). وكان استخدام سلسلة محرف واحد لكل كلمة يُوسَّع، مع ترميز محارف يمثّل معلومات إضافية، لكان أصعب بكثير.
وكما في المحلل النحوي، تتعامل شيفرة توسيع الكلمات مع المحارف التي يتطلّب تمثيلها أكثر من بايت واحد. فمثلًا، يعدّ توسيع الطول (${#variable}) الطول بالمحارف لا بالبايتات، وتستطيع الشيفرة التعرّف بدقّة على نهاية التوسيعات أو المحارف الخاصة بها في وجود المحارف متعددة البايت.
3.6. تنفيذ الأوامر
مرحلة تنفيذ الأوامر في خط bash الداخلي هي حيث يجري العمل الفعلي. ففي معظم الأحيان، تُفكَّك مجموعة الكلمات الموسّعة إلى اسم أمر ومجموعة وسائط، وتُمرَّر إلى نظام التشغيل بوصفها ملفًا يُقرأ ويُنفَّذ، مع تمرير الكلمات المتبقّية بصفتها بقية عناصر argv.
وقد ركّزت الوصفات السابقة عمدًا على ما يُسمّيه Posix الأوامر البسيطة — تلك التي تتكوّن من اسم أمر ومجموعة وسائط. وهذه أكثر أنواع الأوامر شيوعًا، لكن bash يوفّر أكثر من ذلك بكثير.
ومدخل مرحلة تنفيذ الأوامر هو بنية الأمر التي يبنيها المحلل النحوي، ومجموعة كلمات موسّعة. وهنا بالذات تظهر لغة برمجة bash الحقيقية. فاللغة تستخدم المتغيّرات والتوسيعات التي ناقشناها سابقًا، وتنفّذ البنى التي يتوقّعها المرء في لغة عالية المستوى: التكرار، والشروط، والتبديل بين البدائل، والتجميع، والاختيار، والتنفيذ الشرطي المبني على التطابق مع الأنماط، وتقييم التعابير، وعدة بنى أعلى مستوى خاصة بالصدفة.
3.6.1. إعادة التوجيه
من مظاهر دور الصدفة بوصفها واجهة لنظام التشغيل القدرة على إعادة توجيه المدخلات والمخرجات من وإلى الأوامر التي تستدعيها. وصيغة إعادة التوجيه إحدى الأمور التي تكشف عن نضج مستخدمي الصدفة الأوائل: فحتى وقت قريب جدًا كانت تتطلّب من المستخدمين تتبّع واصفات الملفات التي يستخدمونها، وتحديد أي واصف آخر غير المدخل والإخراج والخطأ القياسية برقمه صراحةً.
وقد أضافت صيغة إعادة التوجيه مؤخرًا قدرةً تتيح للمستخدمين توجيه الصدفة لتختار واصف ملفات مناسبًا وتسنده إلى متغيّر محدَّد، بدل أن يختاره المستخدم. فهذا يقلّل العبء الذي يتحمّله المبرمج في تتبّع واصفات الملفات، لكنه يضيف معالجةً إضافية: إذ يتعيّن على الصدفة أن تُكرّر واصفات الملفات في الموضع الصحيح، وأن تتأكد من إسنادها إلى المتغيّر المحدَّد. وهذه مثال آخر على كيفية مرور المعلومات من المحلل اللغوي إلى المحلل النحوي فإلى تنفيذ الأوامر: فالمحلل اللغوي يصنّف الكلمة على أنها إعادة توجيه تتضمّن إسناد متغيّر؛ والمحلل النحوي، في إنتاج القاعدة المناسب، ينشئ كائن إعادة التوجيه مع علم يشير إلى وجوب الإسناد؛ وشيفرة إعادة التوجيه تفسّر ذلك العلم وتحرص على إسناد رقم واصف الملف إلى المتغيّر الصحيح.
وأصعب جزء في تنفيذ إعادة التوجيه هو تذكّر كيفية التراجع عنها. فالقصد من الصدفة أن تخدش التمييز بين الأوامر المنفَّذة من نظام الملفات، والتي تسبّب إنشاء عملية جديدة، وبين الأوامر التي تنفّذها الصدفة نفسها (الأوامر المدمجة). لكن بغضّ النظر عن كيفية تنفيذ الأمر، فلا ينبغي أن تمتدّ آثار إعادات التوجيه إلى ما بعد اكتمال الأمر[2]. ولهذا يتعيّن على الصدفة تتبّع كيفية التراجع عن آثار كل إعادة توجيه، وإلا فإن إعادة توجيه مخرجات أمر مدمج كانت ستغيّر الإخراج القياسي للصدفة. ويعرف bash كيف يتراجع عن كل نوع من إعادات التوجيه، إما بإغلاق واصف ملف خصّصه له، أو بحفظ واصف الملف الذي يجري تكراره ثم استعادته لاحقًا باستخدام dup2. وهذان يستخدمان كائنات إعادة التوجيه نفسها التي ينشئها المحلل النحوي، ويُعالَجان بالدوال نفسها.
ولأن إعادات التوجيه المتعددة منفَّذة بوصفها قوائم بسيطة من الكائنات، فإن إعادات التوجيه المستخدَمة للتراجع عنها تُحفظ في قائمة منفصلة. وتُعالَج هذه القائمة عند اكتمال الأمر، لكن يتعيّن على الصدفة أن تنتبه عندئذ، إذ يجب أن تبقى إعادات التوجيه المرتبطة بدالة من دوال الصدفة أو بالأمر المدمج "." سارية حتى تكتمل تلك الدالة أو ذلك الأمر المدمج. وحين لا يستدعي الأمر المدمج exec أي أمر، فإن قائمة التراجع تُهمَل ببساطة، لأن إعادات التوجيه المرتبطة بـ exec تبقى سارية في بيئة الصدفة.
أمّا التعقيد الآخر فقد أحدثه bash على نفسه. فالإصدارات التاريخية من صدفة Bourne سمحت للمستخدم بالتعامل مع واصفات الملفات من 0 إلى 9 فقط، محجزةً الواصفات 10 فما فوق للاستخدام الداخلي للصدفة. وقد خفّف bash هذا القيد، فسماح للمستخدم بالتعامل مع أي واصف حتى حدّ الملفات المفتوحة في العملية. وهذا يعني أن على bash تتبّع واصفات ملفاته الداخلية، بما فيها تلك التي فتحتها مكتبات خارجية لا الصدفة نفسها، وأن يكون مستعدًا لنقلها عند الطلب. وهذا يتطلّب قدرًا كبيرًا من مسك الدفاتر، وبعض القواعد الاستدلالية المتعلّقة بعلم الإغلاق عند التنفيذ (close-on-exec)، وقائمة أخرى من إعادات التوجيه تُصان طوال مدة تنفيذ الأمر ثم تُعالَج أو تُهمَل.
3.6.2. الأوامر المدمجة
يجعل bash عددًا من الأوامر جزءًا من الصدفة نفسها. وتُنفَّذ هذه الأوامر بواسطة الصدفة من دون إنشاء عملية جديدة.
وأكثر الأسباب شيوعًا لجعل أمر ما أمرًا مدمجًا هو الحفاظ على الحالة الداخلية للصدفة أو تعديلها. وcd مثال جيد؛ فمن بين التمارين الكلاسيكية في مقدمة مواد Unix أن تشرح لماذا لا يمكن تنفيذ cd كأمر خارجي.
تستخدم الأوامر المدمجة في bash الأوّليات الداخلية نفسها التي تستخدمها بقية الصدفة. ويُنفَّذ كل أمر مدمج بدالة من لغة C تأخذ قائمة كلمات كوسائط. وهذه الكلمات هي الكلمات التي يخرج بها طور توسيع الكلمات؛ وتتعامل معها الأوامر المدمجة بوصفها أسماء أوامر ووسائط. وفي معظم الحالات، تستخدم الأوامر المدمجة قواعد التوسيع القياسية نفسها المستخدمة في أي أمر آخر، مع استثناءين: فالأوامر المدمجة في bash التي تقبل جمل الإسناد كوسائط (مثل declare وexport) تستخدم قواعد التوسيع نفسها لوسائط الإسناد التي تستخدمها الصدفة لإسناد المتغيّرات. وهذه إحدى الحالات التي يُستخدم فيها العضو flags في بنية WORD_DESC لتمرير المعلومات بين مرحلة وأخرى في خط الصدفة الداخلي.
3.6.3. تنفيذ الأمر البسيط
الأوامر البسيطة هي الأكثر حضورًا. ويغطّي البحث عن الأوامر المقروءة من نظام الملفات وتنفيذها وجمع حالة خروجها كثيرًا من ميزات الصدفة المتبقية.
وإسنادات متغيّرات الصدفة (أي الكلمات من الشكل var=value) هي نفسها نوع من الأوامر البسيطة. ويجوز أن تسبق جمل الإسناد اسم أمر، أو أن تقف وحدها على سطر أوامر. فإن سبقت أمرًا، فتمرَّر هذه المتغيّرات إلى الأمر المنفَّذ في بيئته (وإن سبقت أمرًا مدمجًا أو دالة من دوال الصدفة، فإنها تبقى، باستثناءات قليلة، طوال تنفيذ ذلك الأمر المدمج أو تلك الدالة فقط). وإذا لم يتبعها اسم أمر، فإن جمل الإسناد تعدّل حالة الصدفة.
وحين يُعرض على bash اسم أمر ليس اسم دالة من دوال الصدفة ولا اسم أمر مدمج، فإن bash يبحث في نظام الملفات عن ملف قابل للتنفيذ بهذا الاسم. وتُستخدم قيمة المتغيّر PATH بوصفها قائمة مجلدات مفصولة بنقطتين يُبحث فيها. أمّا أسماء الأوامر التي تتضمّن شرطة مائلة (أو فواصل مجلدات أخرى) فلا يجري البحث عنها، بل تُنفَّذ مباشرة.
وعند العثور على أمر بالبحث في PATH، يحفظ bash اسم الأمر والمسار الكامل المقابل في جدول تجزئة، يستشيره قبل إجراء عمليات بحث لاحقة في PATH. وإذا لم يُعثر على الأمر، نفّذ bash دالة ذات اسم خاص، إن كانت معرَّفة، مستخدمًا اسم الأمر ووسائطه كوسائط لتلك الدالة. وتستخدم بعض توزيعات Linux هذه الآلية لتعرض تثبيت الأوامر المفقودة.
وإذا عثر bash على ملف لتنفيذه، فإنّه يتفرّع (forks) وينشئ بيئة تنفيذ جديدة، وينفّذ البرنامج في هذه البيئة الجديدة. وبيئة التنفيذ نسخة مطابقة تمامًا من بيئة الصدفة، مع تعديلات طفيفة على أمور مثل معالجة الإشارات، والملفات التي تُفتح وتُغلق بإعادات التوجيه.
3.6.4. التحكم في المهام
يستطيع الصدفة تنفيذ الأوامر في المقدمة، فتنتظر انتهاء الأمر وتجمع حالة خروجه، أو في الخلفية، فتقرأ الصدفة الأمر التالي فورًا. والتحكم في المهام (job control) هو القدرة على نقل العمليات (الأوامر قيد التنفيذ) بين المقدمة والخلفية، وعلى تعليق تنفيذها واستئنافه. ولتنفيذ ذلك، يقدّم bash مفهوم المهمة، وهي في جوهرها أمر ينفّذه عملية واحدة أو أكثر. فخط الأنابيب مثلًا يستخدم عملية واحدة لكل عنصر من عناصره. ومجموعة العمليات (process group) وسيلة لدمج عمليات منفصلة في مهمة واحدة. ولدى الطرفية معرّف مجموعة عمليات مرتبط بها، لذا فإن مجموعة العمليات الأمامية هي التي يكون فيها معرّف مجموعة العمليات مطابقًا لمعرّف مجموعة عمليات الطرفية.
وتستخدم الصدفة في تنفيذ التحكم في المهام عددًا قليلًا من بنى البيانات البسيطة. وهناك بنية تمثّل عملية فرعية، بما في ذلك معرّف العملية وحالتها والحالة التي أعادتها عند انتهاءها. وخط الأنابيب ليس سوى قائمة مترابطة بسيطة من بنى العمليات هذه. والمهمة مشابهة إلى حد كبير: فهناك قائمة عمليات، وحالة مهمة ما (قيد التشغيل، معلّقة، منتهية، إلخ)، ومعرّف مجموعة عمليات المهمة. وقائمة العمليات تتكوّن عادةً من عملية واحدة؛ فخطوط الأنابيب وحدها هي ما يرتبط به أكثر من عملية. ولكل مهمة معرّف مجموعة عمليات فريد، وتُسمّى العملية التي يكون فيها معرّف العملية مساويًا لمعرّف مجموعة عمليات المهمة قائد مجموعة العمليات (process group leader). وتُحفظ مجموعة المهام الحالية في مصفوفة، متطابقة من حيث المبدأ مع الطريقة التي تُعرض بها للمستخدم. وتُجمَّع حالة المهمة وحالة خروجها بتجميع حال العمليات المكوّنة لها.
وكما في عدة أمور أخرى في الصدفة، فإن الجزء المعقّد في تنفيذ التحكم في المهام هو مسك الدفاتر. إذ يتعيّن على الصدفة أن تنتبه لإسناد العمليات إلى مجموعات العمليات الصحيحة، وأن تتأكد من تزامن إنشاء العمليات الفرعية مع إسناد مجموعة العمليات، وأن مجموعة عمليات الطرفية مضبوطة بشكل مناسب، لأن مجموعة عمليات الطرفية تحدّد المهمة الأمامية (وإن لم تُضبط ثانيةً إلى مجموعة عمليات الصدفة، فلن تستطيع الصدفة نفسها قراءة مدخلات الطرفية). ولأنه مرتبط بالعمليات إلى هذا الحد، فإن تنفيذ أوامر مركبة مثل حلقتي while وfor بحيث يمكن إيقاف الحلقة كاملة وبدؤها كوحدة واحدة ليس بالأمر المباشر، وقليل من الأصداف فعل ذلك.
3.6.5. الأوامر المركبة
تتألف الأوامر المركبة من قوائم تتكوّن من أمر بسيط أو أكثر، ويقدّمها كلمة مفتاحية مثل if أو while. وهنا تظهر القوة البرمجية للصدفة أكثر ما تكون ظهورًا وفعالية.
والتنفيذ غير مستغرب إلى حد ما. فالمحلل النحوي ينشئ كائنات تقابل أوامر مركبة مختلفة، ويفسّرها باجتياز الكائن. ولكل أمر مركب دالة C مقابلة مسؤولة عن إجراء التوسيعات المناسبة، وتنفيذ الأوامر على النحو المحدَّد، وتغيير مسار التنفيذ تبعًا لحالة عودة الأمر. والدالة التي تنفّذ أمر for مثال توضيحي. إذ يتعيّن عليها أولًا توسيع قائمة الكلمات التالية للكلمة المحجوزة in. ثم يتعيّن على الدالة أن تتكرّر على الكلمات الموسّعة، فتسنِد كل كلمة إلى المتغيّر المناسب، ثم تنفّذ قائمة الأوامر في جسم أمر for. ولا يتعيّن على أمر for تغيير التنفيذ تبعًا لحالة عودة الأمر، لكنه مضطر إلى الانتباه إلى آثار الأمرين المدمجين break وcontinue. وبعد أن تُستنفد جميع كلمات القائمة، يعود أمر for. وكما يبيّن هذا، فإن التنفيذ يتبع الوصف عن قرب في معظمه.
3.7. الدروس المستفادة
3.7.1. ما وجدته مهمًّا
أمضيت أكثر من عشرين سنة في العمل على bash، وأودّ أن أعتقد أنني اكتشفت بعض الأمور. والأهمّ — وهو أمر لا أُكِّد بما يكفّي — أنه حيوي الاحتفاظ بسجلات تغييرات مفصّلة. فالأمر جيّد عندما تستطيع العودة إلى سجلات التغييرات وتذكّر نفسك بسبب إجراء تغيير بعينه. وهو أفضل عندما تستطيع ربط ذلك التغيير بتقرير خطأ بعينه، متضمّنًا حالة اختبار قابلة لإعادة الإنتاج، أو باقتراح.
وعند المناسبة، فإن الاختبار الانحداري الشامل (regression testing) أمر أنصح ببنائه في المشروع منذ بدايته. ويملك bash آلاف حالات الاختبار التي تغطّي جميع ميزاته غير التفاعلية تقريبًا. وقد فكّرت في بناء اختبارات للميزات التفاعلية — إذ يتوفّر Posix عليها في مجموعة اختبارات المطابقة الخاصة به — لكنني لم أرد أن أُضطر إلى توزيع إطار العمل الذي رأيت أنه سيحتاجه.
والمعايير مهمّة. وقد استفاد bash لأنه تنفيذ لمعيار. ومن المهم المشاركة في توحيد معايير البرمجيات التي تنفّذها. فإلى جانب النقاشات حول الميزات وسلوكها، فإن وجود معيار ترجع إليه بوصفه الحَكَم يمكن أن يكون أمرًا مفيدًا. وبالطبع، يمكن أن يكون سيئًا أيضًا — فذلك يعتمد على المعيار.
والمعايير الخارجية مهمّة، لكن من الجيد أن تكون لديك معايير داخلية أيضًا. وقد حظيت بالحظ الكافي بأن أكون ضمن مجموعة معايير مشروع GNU، وهي تقدّم الكثير من النصائح الجيدة العملية حول التصميم والتنفيذ.
والتوثيق الجيد عنصر أساسي آخر. فإذا كنت تتوقّع أن يُستخدم برنامجك من قِبل آخرين، فإن من الجدي أن تتوفّر له توثيقات شاملة وواضحة. وإذا كان البرنامج ناجحًا، فسينتهي بك المطاف إلى الكثير من التوثيق له، ومن المهم أن يكتب المطوّر النسخة المرجعية.
وهناك الكثير من البرمجيات الجيّدة في الخارج. فاستفد مما تستطيع: فمثلًا، تمتلك gnulib كثيرًا من دوال المكتبات المريحة (ما إن تنزعها من إطار gnulib). وكذلك توزيعات BSD وMac OS X. وقال بيكاسو «الفنانون العظام يسرقون» لسبب وجيه.
وتفاعل مع مجتمع المستخدمين، لكن كن مستعدًا للانتقاد العابر، وبعضه ما سيُحيّر رأسك. فمجتمع المستخدمين النشط يمكن أن يكون فائدة هائلة، لكن أحد نتائج ذلك أن يصبح الناس شديدي الحماس. لا تأخذ الأمر على نحو شخصي.
3.7.2. ما كنت سأفعله بشكل مختلف
يملك bash ملايين المستخدمين. وقد تلقّنت عن أهمية التوافق العكسي. وفي بعض المعاني، يعني التوافق العكسي ألّا تحتاج أبدًا إلى الاعتذار. لكن العالم ليس بهذه البساطة تمامًا. فقد اضطررت إلى إجراء تغييرات غير متوافقة من حين لآخر، وأدّى كل منها تقريبًا إلى عدد من شكاوى المستخدمين، رغم أنني كنت دائمًا أملك ماعتبرته سببًا صالحًا، سواء كان تصحيح قرار سيّئ، أو إصلاح عيب في التصميم، أو تصحيح حالات عدم التوافق بين أجزاء الصدفة. وكنت سأقدّم في وقت أبكر شيئًا يشبه مستويات توافق رسمية لـ bash.
ولم يكن تطوير bash مفتوحًا على نحو خاص أبدًا. وقد ارتقيت فكرة إصدارات المعالم (مثل bash-4.2) وإصدار التصحيحات منفردة. وهناك أسباب لهذا: فأنا أنسّق مع البائعين الذين تتجاوز جداول إصداراتهم ما هو سائد في عالمي البرمجيات الحرة والمصادر المفتوحة، وقد واجهت في الماضي متاعب مع برمجيات البيتا التي يصبح انتشارها أوسع مما أودّ. غير أنني لو بدأت من جديد، لكنت قد فكّرت في إصدارات أكثر تكرارًا، باستخدام نوع من المستودعات العامة.
ولن تكتمل قائمة كهذه دون اعتبار يتعلق بالتنفيذ. فأمر فكّرت فيه عدة مرات، لكنني لم أنفّذه أبدًا، هو إعادة كتابة محلل bash باستخدام التحليل النحوي التعاودي المباشر (recursive-descent) بدلًا من استخدام bison. وقد ظننت مرة أنني سأضطر إلى ذلك كي يصبح استبدال الأمر مطابقًا لـ Posix، لكنني استطعت معالجة تلك المسألة من دون تغييرات بهذا الحجم. ولو كنت أبدأ bash من الصفر، فربّما كنت سأكتب محللًا بيدي. وكان ذلك سيجعل بعض الأمور أسهل قطعًا.
3.8. الخاتمة
bash مثال جيّد على قطعة برمجية حرة كبيرة ومعقّدة. وقد استفاد من أكثر من عشرين سنة من التطوير، وهو ناضج وقوي. فهو يعمل في كل مكان تقريبًا، ويستخدمه ملايين الأشخاص كل يوم، وكثير منهم لا يدرون بذلك.
وتأثر bash بمصادر كثيرة ترجع إلى صدفة Unix الأصلية في الإصدار السابع، التي كتبها Stephen Bourne. وأهمّ هذه المصادر هو معيار Posix، الذي يفرض جزءًا كبيرًا من سلوكه. وقد جلب هذا المزيج بين التوافق العكسي والامتثال للمعايير تحدياته الخاصة.
واستفاد bash لأنه جزء من مشروع GNU، الذي وفّر حركةً وإطارًا يقوم bash داخله. فلو لم يكن GNU لما وُجد bash. وقد استفاد bash أيضًا من مجتمع مستخدميه النشط والمتدفّق. فقد ساعدت ملاحظاتهم في جعل bash ما هو عليه اليوم — شهادة على فوائد البرمجيات الحرة.
الحواشي
- في معظم الحالات، تتوالى أحرف من أحد هذه المحارف.
- الأمر المدمج
execاستثناء لهذه القاعدة.