الفصل المفقود في تعليم علوم الحاسوب › نظرة عامة على المقرر + مقدمة إلى الصدفة 1 / 13 التالي

نظرة عامة على المقرر + مقدمة إلى الصدفة

من نحن؟

يُدرَّس هذا المقرر بشكلٍ مشترك بواسطة Anish وJon وJose. نحن جميعًا طلاب MIT سابقون بدأنا هذا المقرر ضمن برنامج MIT IAP عندما كنّا طلابًا. يمكنكم الوصول إلينا جميعًا عبر missing-semester@mit.edu.

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

الدافع (Motivation)

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

هذا المقرر محاولةٌ لمعالجة هذه المشكلة.

نريد أن نعلّمك كيف تحصل على أقصى فائدةٍ من الأدوات التي تعرفها، وأن نريك أدواتٍ جديدةً تضيفها إلى صندوق أدواتك، ونأمل أن نغرس فيك بعضًا من الحماس لاستكشاف المزيد من الأدوات (وربما بنائها) بنفسك. هذا ما نؤمن بأنه الفصل المفقود (the missing semester) من معظم المناهج الجامعية في علوم الحاسوب.

هيكل المقرر

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

نحاول تغطية قدرٍ كبيرٍ من المادة في بضع محاضراتٍ فقط مدة كلٍّ منها ساعة، لذا تكون المحاضرات كثيفةً نسبيًا. لكي نتيح لك بعض الوقت للتعوّد على المحتوى بوتيرةٍ تناسبك، تتضمّن كل محاضرةٍ مجموعةً من التمارين ترشدك خلال النقاط الأساسية للمحاضرة. لن نخصّص ساعاتٍ مكتبيةً للمراجعة، لكنّنا نشجّعك على طرح الأسئلة على Discord الخاص بـ OSSU في قناة #missing-semester-forum، أو مراسلتنا على missing-semester@mit.edu.

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

أخيرًا، إذا كانت لديك أيّ ملاحظات حول المقرر، فيُرجى إرسالها إلينا عبر البريد على missing-semester@mit.edu.

الموضوع الأوّل: مُعالِج الأوامر (The Shell)

ما هو مُعالِج الأوامر؟

تمتلك الحواسيب هذه الأيام أنواعًا متعددةً من الواجهات (interfaces) لإعطائها الأوامر؛ واجهات مستخدمٍ رسوميةٍ متقنة، وواجهات صوتية، وواقعًا معززًا وافتراضيًا، ومؤخرًا: نماذج اللغة واسعة النطاق (LLMs). هذه الواجهات رائعةٌ في 80% من حالات الاستخدام، لكنها غالبًا مقيّدةٌ جوهريًا فيما تتيح لك القيام به؛ فلا يمكنك الضغط على زرٍّ غير موجود، ولا إعطاء أمرٍ صوتيٍّ لم تتم برمجته. للاستفادة الكاملة من الأدوات التي يوفّرها حاسوبك، علينا أن نعود إلى الطريقة التقليدية وننزل إلى واجهةٍ نصية: ألا وهي مُعالِج الأوامر (the Shell).

جميع المنصّات التي تقع بين يديك تقريبًا لديها مُعالج أوامرٍ بشكلٍ أو بآخر، وكثيرٌ منها يوفّر لك عدّة معالجاتٍ للاختيار من بينها. ورغم أنّها قد تختلف في التفاصيل، فهي في جوهرها متشابهةٌ كلها: تتيح لك تشغيل البرامج، وإمدادها بالمدخلات، وفحص مخرجاتها بطريقةٍ شبه منظّمة.

لفتح موجِّه (prompt) مُعالج الأوامر (حيث يمكنك كتابة الأوامر)، تحتاج أولًا إلى مُحاكٍ طرفي (terminal)، وهو الواجهة البصرية لمُعالج الأوامر. على الأرجح أنّ جهازك يأتي مع واحدٍ مثبّتٍ مسبقًا، أو يمكنك تثبيت واحدٍ بسهولةٍ كبيرة:

  • Linux: اضغط Ctrl Alt T (يعمل على معظم التوزيعات). أو ابحث عن «Terminal» في قائمة التطبيقات.
  • Windows: اضغط Win R، ثم اكتب cmd أو powershell واضغط Enter. بدلًا من ذلك، ابحث عن «Terminal» أو «Command Prompt» في قائمة ابدأ.
  • macOS: اضغط Cmd Space لفتح Spotlight، ثم اكتب «Terminal» واضغط Enter. أو ابحث عنه في التطبيقات ← الأدوات ← Terminal.

على Linux وmacOS، سيفتح هذا عادةً مُعالج أوامر Bourne Again SHell، أو «bash» اختصارًا. هذا واحدٌ من أكثر معالجات الأوامر استخدامًا، وصياغته (syntax) مشابهةٌ لما ستراه في كثيرٍ من معالجات الأوامر الأخرى. على Windows، سيستقبلك مُعالجا الأوامر «batch» أو «powershell»، وذلك حسب الأمر الذي شغّلته. هذان خاصّان بـ Windows، ولن نركّز عليهما في هذا المقرر، رغم وجود ما يناظر معظم ما سنعلّمه في غيرهما. ستحتاج بدلًا منهما إلى نظام Windows الفرعي لـ Linux (Windows Subsystem for Linux) أو جهازٍ افتراضيٍّ يعمل بنظام Linux.

توجد معالجات أوامر أخرى، كثيرٌ منها يقدّم تحسيناتٍ مريحةٍ على bash (من أكثرها شيوعًا fish وzsh). ورغم أنّها شائعةٌ جدًا (جميع المحاضرين يستخدمون أحدها)، فهي أبعد ما تكون عن الانتشار الواسع الذي بلغه bash، وتعتمد على كثيرٍ من المفاهيم نفسها، لذا لن نركّز عليها في هذه المحاضرة.

لماذا ينبغي أن تهتمّ به؟

لا يقتصر الأمر على أنّ مُعالج الأوامر (عادةً) أسرع بكثيرٍ من «النقر هنا وهناك»، بل إنّه أيضًا يمتلك قوةً تعبيريةً يصعب العثور عليها في أيّ برنامجٍ رسوميٍّ واحد. كما سنرى، يمنحك مُعالج الأوامر القدرة على دمج البرامج بطرقٍ خلّاقة لأتمتة أيّ مهمةٍ تقريبًا.

الإلمام بمُعالج الأوامر مفيدٌ جدًا أيضًا للتجوال في عالم البرمجيات مفتوحة المصدر (التي غالبًا ما تأتي مع تعليمات تثبيتٍ تتطلب مُعالج الأوامر)، ولبناء التكامل المستمر (continuous integration) لمشاريعك البرمجية (كما هو موصوف في محاضرة جودة الكود)، ولتشخيص الأخطاء عندما تفشل البرامج الأخرى.

التنقّل في مُعالج الأوامر

عند تشغيل مُحاكيك الطرفي، سترى موجِّهًا (prompt) يبدو غالبًا هكذا تقريبًا:

missing:~$

هذه هي الواجهة النصية الرئيسية لمُعالج الأوامر. تخبرك بأنّك على الجهاز المسمّى missing، وأنّ «مجلد العمل الحالي» (current working directory) — أي المكان الذي تتواجد فيه الآن — هو ~ (اختصارٌ لكلمة «home» أي المنزل). يُخبرك الرمز $ بأنّك لست المستخدم الجذر (root user) (سنأتي لذلك لاحقًا). عند هذا الموجّه يمكنك كتابة أمرٍ (command) يُفسّره مُعالج الأوامر. الأمر الأساسي هو تشغيل برنامج:

missing:~$ date
Fri 10 Jan 2020 11:49:31 AM EST
missing:~$

هنا، أجرينا برنامج date، الذي (ربما دون مفاجأة) يطبع التاريخ والوقت الحاليين. ثم يطلب منّا مُعالج الأوامر أمرًا آخر لتنفيذه. يمكننا أيضًا تنفيذ أمرٍ مع وسائط (arguments):

missing:~$ echo hello
hello

في هذه الحالة، أخبرنا مُعالج الأوامر أن يُنفّذ البرنامج echo مع الوسيط hello. برنامج echo يطبع وسائطه ببساطةٍ. ينسّق مُعالج الأوامر الأمرَ بتقسيمه على المسافات البيضاء، ثم يشغّل البرنامج المشار إليه بالكلمة الأولى، مُسلّمًا كل كلمةٍ لاحقةٍ كوسيطٍ يمكن للبرنامج الوصول إليه. إذا أردت تمرير وسيطٍ يحتوي مسافاتٍ أو رموزًا خاصةً (مثل مجلدٍ اسمه «My Photos»)، فإمّا أن تضع الوسيط بين علامتَي اقتباس ' أو " (مثل "My Photos")، أو تُفلت (escape) الحروف ذات الصلة فقط بخطٍ مائلٍ عكسيّ \ (مثل My\ Photos).

ربما يكون أهم أمرٍ عند البدء هو man، والمختصر لـ «manual» أي الدليل. يتيح لك برنامج man، ضمن أشياء أخرى، البحث عن معلوماتٍ إضافية حول أيّ أمرٍ في نظامك. على سبيل المثال، إذا شغّلت man date، فسيوضّح لك ما هو date وكلّ الوسائط المختلفة التي يمكنك تمريرها إليه لتغيير سلوكه. وعادةً يمكنك أيضًا الحصول على نسخةٍ قصيرةٍ من المساعدة بتمرير --help كوسيطٍ لأغلب الأوامر.

ننصحك بتثبيت واستخدام tldr إلى جانب man، إذ يعرض لك أمثلة الاستخدام الشائعة هناك مباشرةً في المُحاكي الطرفي. كما أنّ نماذج اللغة (LLMs) عادةً بارعةٌ جدًا في شرح كيف تعمل الأوامر وكيف يمكنك استدعاؤها لتحقيق ما تريد إنجازه.

بعد man، أهم أمرٍ يجب تعلّمه هو cd، أي «تغيير المجلد» (change directory). هذا الأمر مدمجٌ فعلًا في مُعالج الأوامر، وهو ليس برنامجًا منفصلًا (أي أنّ which cd ستقول «no cd found»). تمرّر له مسارًا، فيصبح ذلك المسار هو مجلد عملك الحالي. وسترى أيضًا مجلدَ العمل منعكسًا في موجّه مُعالج الأوامر:

missing:~$ cd /bin
missing:/bin$ cd /
missing:/$ cd ~
missing:~$

لاحظ أنّ مُعالج الأوامر يأتي مع إكمالٍ تلقائي، لذا يمكنك غالبًا إكمال المسارات بشكلٍ أسرع بالضغط على <TAB>!

كثيرٌ من الأوامر تعمل على مجلد العمل الحالي إذا لم يُحدَّد شيءٌ آخر. إذا لم تكن متأكدًا في أي وقتٍ من مكانك، يمكنك تشغيل pwd أو طباعة المتغير البيئي (environment variable) $PWD (باستخدام echo $PWD)، وكلاهما يُخرج مجلد العمل الحالي.

كما أنّ مجلد العمل الحالي مفيدٌ في أنه يتيح لنا استخدام مساراتٍ نسبية (relative). جميع المسارات التي رأيناها حتى الآن كانت مطلقة (absolute)؛ فهي تبدأ بـ / وتُعطي السلسلة الكاملة من المجلدات اللازمة للانتقال إلى موقعٍ ما انطلاقًا من جذر نظام الملفات (/). عمليًا، ستتعامل غالبًا مع المسارات النسبية؛ إذ تُسمّى كذلك لأنها نسبيةٌ إلى مجلد العمل الحالي. في المسار النسبيّ (أيّ شيءٍ لا يبدأ بـ /)، يُبحث عن المكوّن الأول للمسار في مجلد العمل الحالي، وتعبُر المكوّنات اللاحقة كالمعتاد. على سبيل المثال:

missing:~$ cd /
missing:/$ cd bin
missing:/bin$

هناك أيضًا مكوّنان «خاصّان» موجودان في كلّ مجلد: . و... حيث . تعني «هذا المجلد»، و.. تعني «المجلد الأب» (parent directory). إذًا:

missing:~$ cd /
missing:/$ cd bin/../bin/../bin/././../bin/..
missing:/$

يمكنك عادةً استخدام المسارات المطلقة والنسبية بالتبادل مع أيّ وسيطٍ للأوامر، فقط تذكّر مجلدَ عملك الحالي عند استخدام مسارٍ نسبيّ!

ننصحك بتثبيت واستخدام zoxide لتسريع تنقّلاتك بـ cd؛ إذ سيتذكر z المسارات التي تتردّد عليها كثيرًا ويتيح لك الوصول إليها بكتابةٍ أقلّ.

ما المتاح في مُعالج الأوامر؟

لكن كيف يعرف مُعالج الأوامر كيفية العثور على برامجٍ مثل date أو echo؟ عندما يُطلب من مُعالج الأوامر تنفيذ أمرٍ، فإنّه يستشير متغيرًا بيئيًا يُسمّى $PATH يدرج الأدلةَ (directories) التي ينبغي لمُعالج الأوامر أن يبحث فيها عن البرامج عندما يُعطى أمرًا:

missing:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
missing:~$ which echo
/bin/echo
missing:~$ /bin/echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

عندما نشغّل أمر echo، يرى مُعالج الأوامر أنّه ينبغي تنفيذ البرنامج echo، ثم يبحث في القائمة المفصولة بالفواصل النقطية : للأدلة داخل $PATH عن ملفٍ بهذا الاسم. وعندما يجده، يشغّله (بافتراض أنّ الملف قابل للتنفيذ executable؛ سنأتي لذلك لاحقًا). يمكننا معرفة الملف الذي يُنفَّذ لاسم برنامجٍ معيّن باستخدام برنامج which. يمكننا أيضًا تجاوز $PATH كليًا بإعطاء مسار الملف الذي نريد تنفيذه.

يعطينا هذا أيضًا فكرةً عن كيفية تحديد كل البرامج التي يمكننا تنفيذها في مُعالج الأوامر: بسرد محتويات جميع الأدلة الواقعة على $PATH. يمكننا ذلك بتمرير مسار دليلٍ معيّن إلى برنامج ls، الذي يعرض قائمة الملفات:

missing:~$ ls /bin

ننصحك بتثبيت واستخدام eza للحصول على ls أكثر ودًّا مع البشر.

سيعرض هذا — على أغلب الحواسيب — قدرًا كبيرًا من البرامج، لكننا سنركّز هنا على بعضٍ من أهمّها فقط. أولًا، بعض الأوامر البسيطة:

  • cat file، الذي يطبع محتويات file.
  • sort file، الذي يطبع أسطر file بترتيبٍ مُفرز.
  • uniq file، الذي يزيل الأسطر المتطابقة المتتالية من file.
  • head file وtail file، اللذان يطبعان أول وآخر بضعة أسطرٍ من file على الترتيب.

ننصحك بتثبيت واستخدام bat بدلًا من cat لأجل تلوين الصياغة (syntax highlighting) وإمكانية التمرير.

هناك أيضًا grep pattern file، الذي يعثر على الأسطر المطابقة للنمط pattern في file. هذا الأمر يستحقّ اهتمامًا أكبر قليلًا لأنه مفيدٌ جدًا وفي الوقت نفسه يتميّز بمجموعة ميزاتٍ أوسع مما قد تتوقّع. pattern هو في الحقيقة تعبيرٌ نمطيّ (regular expression) يمكنه التعبير عن أنماطٍ معقدةٍ للغاية؛ وسنتناول تلك الأنماط في محاضرة جودة الكود. يمكنك أيضًا تحديد دليلٍ بدلًا من ملف (أو تركه فارغًا للمجلد .)، وتمرير -r للبحث المتكرّر في جميع ملفات الدليل.

ننصحك بتثبيت واستخدام ripgrep بدلًا من grep للحصول على بديلٍ أسرع وأكثر ودًّا مع البشر (لكنّه أقلّ قابليةً للنقل). كما أنّ ripgrep سيبحث بشكلٍ متكرّر في مجلد العمل الحالي افتراضيًا!

ثمّة أيضًا بعض الأدوات المفيدة جدًا ذات واجهةٍ أكثر تعقيدًا قليلًا. أولاها sed، وهو محرّر ملفاتٍ برمجيّ (programmatic). لديه لغة برمجةٍ خاصة به لإجراء تعديلاتٍ مؤتمتةٍ على الملفات، لكنّ أكثر استخداماته شيوعًا هو:

missing:~$ sed -i 's/pattern/replacement/g' file

يستبدل هذا جميع حالات pattern بـ replacement في file. يشير -i إلى رغبتنا في إجراء الاستبدالات داخل الملف نفسه (inline) (بدلًا من ترك file دون تعديلٍ وطباعة المحتويات المستبدلة). ويعبّر s/ عن رغبتنا، في لغة برمجة sed، في إجراء استبدال. ويفصل / بين النمط والبديل. وتشير اللاحقة /g إلى رغبتنا في استبدال جميع الوقوعات في كل سطرٍ وليس الأول فحسب. وكما في grep، فإنّ pattern هنا تعبيرٌ نمطيّ، وهو ما يمنحك قوةً تعبيريةً كبيرة. كما تسمح استبدالات التعبيرات النمطية للبديل replacement بالإحالة إلى أجزاءٍ من النمط المطابَق؛ سنرى مثالًا على ذلك بعد قليل.

بعد ذلك لدينا find، الذي يتيح لك العثور على ملفاتٍ (بشكلٍ متكرّر) تطابق شروطًا معيّنة. على سبيل المثال:

missing:~$ find ~/Downloads -type f -name "*.zip" -mtime +30

يعثر على ملفات ZIP في مجلد التنزيلات الأقدم من 30 يومًا.

missing:~$ find ~ -type f -size +100M -exec ls -lh {} \;

يعثر على الملفات الأكبر من 100 ميجابايت في مجلد منزلك ويعرضها. لاحظ أنّ -exec يأخذ أمرًا يُختتم بفاصلةٍ منقوطةٍ منفردة ; (نحتاج لإفلاتها تمامًا مثل المسافة) حيث يُستبدل {} بمسار كلّ ملفٍ مطابَق بواسطة find.

missing:~$ find . -name "*.py" -exec grep -l "TODO" {} \;

يعثر على أيّ ملفات .py تحتوي عناصر TODO فيها.

قد تكون صياغة find شاقةً بعض الشيء، لكن نأمل أن يمنحك هذا إحساسًا بمدى فائدته!

ننصحك بتثبيت واستخدام fd بدلًا من find للحصول على تجربةٍ أكثر ودًّا مع البشر (ولكنها أقلّ قابليةً للنقل!).

التالي في القائمة هو awk، الذي لديه — مثل sed — لغة برمجةٍ خاصة به. حيث بُني sed لتحرير الملفات، بينما بُني awk لتحليلها (parsing). أكثر استخدامات awk شيوعًا بدرجةٍ كبيرةٍ هي لملفات البيانات ذات الصياغة المنتظمة (مثل ملفات CSV) حيث تريد استخراج أجزاءٍ معيّنة فقط من كلّ سجلّ (أي سطر):

missing:~$ awk '{print $2}' file

يطبع العمود الثاني (الفاصل بين أجزائه مسافاتٌ بيضاء) من كل سطرٍ في file. وإذا أضفت -F,، فسيطبع العمود الثاني المفصول بفواصلَ من كل سطر. يمكن لـ awk أن يفعل أكثر بكثير — تصفية الصفوف، وحساب المجاميع، والمزيد — راجع التمارين لكي تتذوّق شيئًا من ذلك.

إذا جمعنا هذه الأدوات معًا، يمكننا القيام بأمورٍ فاخرةٍ مثل:

missing:~$ ssh myserver 'journalctl -u sshd -b-1 | grep "Disconnected from"' \
  | sed -E 's/.*Disconnected from .* user (.*) [^ ]+ port.*/\1/' \
  | sort | uniq -c \
  | sort -nk1,1 | tail -n10 \
  | awk '{print $2}' | paste -sd,
postgres,mysql,oracle,dell,ubuntu,inspur,test,admin,user,root

وهذا يجلب سجلات SSH من خادمٍ بعيد (سنتحدث أكثر عن ssh في المحاضرة التالية)، ويبحث عن رسائل قطع الاتصال، ويستخرج اسم المستخدم من كلّ رسالةٍ من تلك الرسائل، ويطبع أسماء المستخدمين العشرة الأوائل مفصولةً بفواصل. كل ذلك في أمرٍ واحد! وسنترك تشريح كل خطوةٍ من هذه الخطوات كتمرينٍ لك.

لغة مُعالج الأوامر (bash)

قدّم المثال السابق مفهومًا جديدًا: الأنابيب (|). هذه تتيح لك وصل مخرجات برنامجٍ ما بمدخلات برنامجٍ آخر. ويعمل هذا لأنّ معظم برامج سطر الأوامر ستتعامل مع «المدخل القياسي» (standard input) (حيث تذهب ضغطاتك على لوحة المفاتيح عادةً) إذا لم يُعطَ الوسيط file. يأخذ | «المخرج القياسي» (standard output) (ما يُطبع عادةً على مُحاكيك الطرفي) للبرنامج الذي يسبق | ويجعله هو المدخل القياسي للبرنامج الذي يليه |. يتيح لك هذا تأليف (compose) برامج مُعالج الأوامر، وهو جزءٌ مما يجعل مُعالج الأوامر بيئةَ عملٍ منتجةً للغاية!

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

أولًا، عمليات إعادة التوجيه (redirections): يتيح لك >file أخذ المخرج القياسي لبرنامجٍ ما وكتابته إلى file بدلًا من مُحاكيك الطرفي. وهذا يجعل تحليله لاحقًا أسهل. بينما يلحق >>file بـ file بدلًا من الكتابة فوقه. وهناك أيضًا <file الذي يخبر مُعالج الأوامر بالقراءة من file بدلًا من لوحة المفاتيح كمدخلٍ قياسيٍّ لبرنامجٍ ما.

هذا وقتٌ مناسبٌ لذكر برنامج tee. سيطبع tee المدخل القياسي إلى المخرج القياسي (تمامًا مثل cat!)، لكنه سيكتبه أيضًا إلى ملف. إذًا ستحفظ جملة verbose cmd | tee verbose.log | grep CRITICAL السجلّ الكامل المفصّل إلى ملفٍ مع إبقاء مُحاكيك الطرفي نظيفًا!

بعد ذلك، الجمل الشرطية: ستنفّذ if command1; then command2; command3; fi الأمرَ command1، وإذا لم ينتج عن ذلك خطأً، فستشغّل command2 وcommand3. ويمكنك أيضًا الحصول على فرع آخر else إذا رغبت. أكثر أمرٍ استخدامًا بصفتها command1 هو أمر test، والذي يُختصر غالبًا ببساطةٍ إلى [، ويتيح لك تقييم شروطٍ مثل «هل يوجد ملف؟» (test -f file أو [ -f file ]) أو «هل تساوي سلسلةٌ أخرى؟» ([ "$var" = "string" ]). في bash، يوجد أيضًا [[ ]]، وهي نسخةٌ مدمجةٌ «أكثر أمانًا» من test ولديها سلوكياتٌ غريبةٌ أقلّ حيال الاقتباس.

لدى bash أيضًا نموذجان من الحلقات، while وfor. تعمل while command1; do command2; command3; done تمامًا مثل المقابل الشرطي if، باستثناء أنها ستعيد تنفيذ كلّ شيءٍ مرارًا وتكرارًا ما دام command1 لا ينتج خطأً. تنفّذ for varname in a b c d; do command; done الأمرَ command أربع مرات، وفي كل مرةٍ يُضبط $varname على واحدٍ من a وb وc وd. وبدلًا من سرد العناصر صراحةً، ستستخدم غالبًا «استبدال الأوامر» (command substitution)، مثل:

for i in $(seq 1 10); do

ينفّذ هذا الأمرَ seq 1 10 (الذي يطبع الأرقام من 1 إلى 10 شاملةً) ثم يستبدل كامل $() بمخرج ذلك الأمر، مانحًا إياك حلقةً من 10 تكرارات. في التعليمات البرمجية الأقدم سترى أحيانًا علاماتِ اقتباسٍ خلفيةً حرفية (مثل for i in `seq 1 10`; do) بدلًا من $()، لكن ينبغي أن تُفضّل وبشدّةٍ صيغة $() لأنها قابلةٌ للتضمين (nesting).

رغم أنه يمكنك كتابة نصوصٍ برمجيةٍ (scripts) طويلةٍ مباشرةً في الموجّه، ستريد عادةً كتابتها في ملفٍ .sh بدلًا من ذلك. على سبيل المثال، هذا نصّ برمجيٌّ سيشغّل برنامجًا في حلقةٍ حتى يفشل، طابعًا مخرج التشغيل الفاشل فقط، مع إجهاد معالجك في الخلفية (مفيدٌ لإعادة إنتاج الاختبارات غير المستقرة مثلًا):

#!/bin/bash
set -euo pipefail

# Start CPU stress in background
stress --cpu 8 &
STRESS_PID=$!

# Setup log file
LOGFILE="test_runs_$(date +%s).log"
echo "Logging to $LOGFILE"

# Run tests until one fails
RUN=1
while cargo test my_test > "$LOGFILE" 2>&1; do
    echo "Run $RUN passed"
    ((RUN++))
done

# Cleanup and report
kill $STRESS_PID
echo "Test failed on run $RUN"
echo "Last 20 lines of output:"
tail -n 20 "$LOGFILE"
echo "Full log: $LOGFILE"

يحتوي هذا على عددٍ من الأمور الجديدة التي أنصحك بقضاء بعض الوقت في استكشافها، إذ إنها مفيدةٌ جدًا في صياغة استدعاءات مُعالج أوامر مفيدةٍ مثل الوظائف الخلفية (&) لتشغيل البرامج بالتزامن، وعمليات إعادة توجيه مُعالج الأوامر الأكثر تعقيدًا، والتوسيع الحسابي (arithmetic expansion).

لكن من الجدير قضاء ثانيةٍ في أول سطرين من البرنامج. الأول هو «الـ shebang»؛ سترى هذا في أعلى ملفاتٍ أخرى غير نصوص مُعالج الأوامر أيضًا. عندما يُنفَّذ ملفٌ يبدأ بالعبارة السحرية #!/path، سيشغّل مُعالج الأوامر البرنامجَ الموجود في /path، ويُمرّر إليه محتويات الملف كمدخل. وفي حالة نصٍ برمجيّ لمُعالج الأوامر، فهذا يعني تمرير محتويات النصّ البرمجيّ إلى /bin/bash، لكن يمكنك أيضًا كتابة نصوصٍ برمجيةٍ Python مع سطر shebang من /usr/bin/python!

أما السطر الثاني فيُعدّ وسيلةً لجعل bash «أكثر تشدّدًا»، وتخفيف عددٍ من مصادر الخلل (footguns) عند كتابة النصوص البرمجية. يمكن لـ set أن تأخذ عددًا كبيرًا من الوسائط، لكن باختصار: يجعل -e النص البرمجي يخرج مبكرًا إذا فشل أيّ أمر؛ ويجعل -u استخدامَ المتغيرات غير المعرّفة يُسقط النص بدلًا من مجرد استخدام سلسلةٍ فارغة؛ ويجعل -o pipefail النص البرمجي ككلًّا يخرج مبكرًا إذا فشلت البرامج في تسلسل |.

البرمجة بمُعالج الأوامر موضوعٌ عميقٌ، تمامًا كما أيّ لغة برمجة، لكن حذارِ: يمتلك bash عددًا غير عاديّ من المزالق، إلى حدّ وجود عدة مواقع مخصّصةٍ لسردها. أنصح بشدّةٍ باستخدام مكثّفٍ لـ shellcheck عند كتابتها. كما أنّ نماذج اللغة (LLMs) رائعةٌ في كتابة نصوص مُعالج الأوامر وتصحيح أخطائها، وكذلك في ترجمتها إلى «لغة برمجة حقيقية» (مثل Python) عندما تكبر أكثر من اللازم ليحتملها bash (أكثر من 100 سطر).

الخطوات التالية

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

تمارين

تصاحب كلّ محاضرات هذا المقرر سلسلةٌ من التمارين. بعضها يعطيك مهمةً محدّدةً للقيام بها، بينما البعض الآخر مفتوح النهاية، مثل «جرّب استخدام X وY». نشجّعك بشدّةٍ على تجربتها.

لم نكتب حلولًا للتمارين. إذا كنت عالقًا في أمرٍ معيّن، فلا تتردّد في النشر في #missing-semester-forum على Discord أو في مراسلتنا عبر البريد موصوفًا ما جربته حتى الآن، وسنحاول مساعدتك. من المرجّح أيضًا أن تعمل هذه التمارين جيدًا كأوامر أولية (prompts) في محادثةٍ مع نموذج لغة (LLM) حيث يمكنك الانغماس فيها تفاعليًا مع الموضوع. القيمة الحقيقية في هذه التمارين هي رحلة اكتشاف الإجابات، لا الإجابة نفسها. نشجّعك على تتبّع الفروع الجانبية وطرح سؤال «لماذا» أثناء العمل عليها، بدلًا من مجرد البحث عن أقصر طريقٍ إلى الحل.

  1. لهذا المقرر، يجب أن تستخدم مُعالج أوامر يونكس (Unix) مثل Bash أو ZSH. إذا كنت على Linux أو macOS، فلست مضطرًا لفعل أيّ شيءٍ خاص. أما إذا كنت على Windows، فعليك التأكد من أنّك لا تشغّل cmd.exe أو PowerShell؛ يمكنك استخدام نظام Windows الفرعي لـ Linux أو جهازٍ افتراضيٍّ يعمل بنظام Linux لاستخدام أدوات سطر الأوامر بنمط يونكس. وللتأكد من تشغيلك لمُعالج أوامرٍ مناسب، يمكنك تجربة الأمر echo $SHELL. إذا طبع شيئًا مثل /bin/bash أو /usr/bin/zsh، فهذا يعني أنّك تشغّل البرنامج الصحيح.

  2. ماذا يفعل العَلم (flag) -l الخاص بـ ls؟ شغّل ls -l / وافحص المخرجات. ماذا تعني أول 10 رموزٍ في كل سطر؟ (تلميح: man ls)

  3. في الأمر find ~/Downloads -type f -name "*.zip" -mtime +30، فإنّ *.zip هو «glob». ما هو glob؟ أنشئ مجلد تجاربٍ يحتوي بعض الملفات، وجرّب أنماطًا مثل ls *.txt وls file?.txt وls {a,b,c}.txt. راجع مطابقة الأنماط في دليل Bash.

  4. ما الفرق بين 'علامات الاقتباس المفردة' (single quotes)، و"علامات الاقتباس المزدوجة" (double quotes)، و$'علامات اقتباس ANSI'؟ اكتب أمرًا يصدّر سلسلةً تحتوي $ حرفيًا و! وحرف سطرٍ جديد. راجع الاقتباس (Quoting).

  5. يملك مُعالج الأوامر ثلاثة دفقاتٍ (streams) قياسية: stdin (0)، وstdout (1)، وstderr (2). شغّل ls /nonexistent /tmp وأعد توجيه stdout إلى ملفٍ وstderr إلى ملفٍ آخر. كيف تعيد توجيه كلاهما إلى الملف نفسه؟ راجع عمليات إعادة التوجيه.

  6. يحمل $? حالة الخروج (exit status) للأمر الأخير (0 تعني النجاح). ينفّذ && الأمرَ التالي فقط إذا نجح السابق؛ وينفّذه || فقط إذا فشل السابق. اكتب سطرًا واحدًا يُنشئ /tmp/mydir فقط إذا لم يكن موجودًا فعلًا. راجع حالة الخروج.

  7. لماذا يجب أن يكون cd مدمجًا في مُعالج الأوامر نفسه بدلًا من أن يكون برنامجًا مستقلًا؟ (تلميح: فكّر فيما يمكن للعملية الفرعية (child process) وما لا يمكنها أن تؤثّر فيه داخل عمليتها الأصلية.)

  8. اكتب نصًّا برمجيًا (script) يأخذ اسم ملفٍ كوسيطٍ ($1) ويتحقق مما إذا كان الملف موجودًا باستخدام test -f أو [ -f ... ]. يجب أن يطبع رسائلَ مختلفةً بحسب وجود الملف. راجع التعبيرات الشرطية في Bash.

  9. احفظ النص البرمجي من التمرين السابق في ملفٍ (مثلًا check.sh). جرّب تشغيله باستخدام ./check.sh somefile. ماذا يحدث؟ الآن شغّل chmod +x check.sh وجرّب مرةً أخرى. لماذا هذه الخطوة ضرورية؟ (تلميح: انظر إلى ls -l check.sh قبل chmod وبعده.)

  10. ماذا يحدث إذا أضفت -x إلى أعلام (flags) set في نصٍّ برمجي؟ جرّب ذلك مع نصٍّ برمجيٍّ بسيطٍ ولاحظ المخرجات. راجع المدمجة Set.

  11. اكتب أمرًا ينسخ ملفًا إلى نسخةٍ احتياطية (backup) يحمل تاريخ اليوم في اسم الملف (مثلًا notes.txt ← notes_2026-01-12.txt). (تلميح: $(date +%Y-%m-%d)). راجع استبدال الأوامر.

  12. عدّل نصّ الاختبار غير المستقر (flaky test script) من المحاضرة ليقبل أمر الاختبار نفسه كوسيطٍ بدلًا من ترميز cargo test my_test بنصٍّ ثابت (hardcoded). (تلميح: $1 أو $@). راجع المعلمات الخاصة.

  13. استخدم أنابيب (pipes) للعثور على امتدادات الملفات (file extensions) الخمسة الأكثر شيوعًا في مجلد منزلك. (تلميح: ادمج find وgrep أو sed أو awk وsort وuniq -c وhead).

  14. يحوّل xargs الأسطر من stdin إلى وسائط أوامر. استخدم find وxargs معًا (وليس find -exec) للعثور على جميع ملفات .sh في مجلدٍ ما وإحصاء الأسطر في كلٍّ منها باستخدام wc -l. مكافأة: اجعله يتعامل مع أسماء الملفات التي تحتوي مسافات. (تلميح: -print0 و-0). راجع man xargs.

  15. استخدم curl لجلب HTML الخاص بموقع المقرر (https://missing.csail.mit.edu/) وتوجيهه إلى grep لإحصاء عدد المحاضرات المدرجة. (تلميح: ابحث عن نمطٍ يظهر مرةً واحدةً لكل محاضرة؛ واستخدم curl -s لإسكات مخرجات التقدّم.)

  16. jq أداةٌ قويةٌ لمعالجة بيانات JSON. اجلب بيانات العيّنة من https://microsoftedge.github.io/Demos/json-dummy-data/64KB.json باستخدام curl واستخدم jq لاستخراج أسماء الأشخاص الذين إصدارهم أكبر من 6 فقط. (تلميح: مرّر إلى jq . أولًا لرؤية البنية؛ ثم جرّب jq '.[] | select(...) | .name')

  17. يمكن لـ awk تصفية الأسطر اعتمادًا على قيم الأعمدة ومعالجة المخرجات. على سبيل المثال، تطبع جملة awk '$3 ~ /pattern/ {$4=""; print}' الأسطر الوحيدة التي يطابق عمودها الثالث pattern، مع حذف العمود الرابع. اكتب أمر awk يطبع الأسطر الوحيدة التي يكون عمودها الثاني أكبر من 100، ويُبدّل العمودين الأول والثالث. جرّب باستخدام: printf 'a 50 x\nb 150 y\nc 200 z\n'

  18. شتّت خط أنابيب سجلات SSH من المحاضرة: ماذا تفعل كل خطوة؟ ثم ابنِ شيئًا مشابهًا للعثور على أوامر مُعالج الأوامر الأكثر استخدامًا لديك من ~/.bash_history (أو ~/.zsh_history).