الفصل المفقود في تعليم علوم الحاسوب › بيئة سطر الأوامر 2 / 13 السابق التالي

بيئة سطر الأوامر

كما تناولنا في المحاضرة السابقة، فإن معظم مُعالِجات الأوامر (Shells) ليست مجرد أداة إطلاق لبرامج أخرى، بل توفّر في الواقع لغة برمجة كاملة مليئة بالأنماط والتجريدات الشائعة. لكن على عكس معظم لغات البرمجة، فإن كل شيء في نصوص مُعالج الأوامر (Shell Scripting) مصمَّم حول تشغيل البرامج وجعلها تتواصل مع بعضها البعض ببساطة وكفاءة.

على وجه الخصوص، يرتبط نصوص مُعالج الأوامر ارتباطًا وثيقًا بـ الاصطلاحات (Conventions). لكي يتعامل برنامج واجهة سطر الأوامر (Command Line Interface - CLI) بشكل جيد مع بيئة مُعالج الأوامر الأوسع، هناك بعض الأنماط الشائعة التي يجب أن يتبعها. سنغطي الآن الكثير من المفاهيم المطلوبة لفهم كيفية عمل برامج سطر الأوامر، بالإضافة إلى الاصطلاحات الشائعة لكيفية استخدامها وتكوينها.

واجهة سطر الأوامر (The Command Line Interface)

كتابة دالة في معظم لغات البرمجة تبدو تقريبًا هكذا:

def add(x: int, y: int) -> int:
    return x + y

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

#!/usr/bin/env bash

if [[ -f $1 ]]; then
    echo "Target file already exists"
    exit 1
else
    if $DEBUG; then
        grep 'error' - | tee $1
    else
        grep 'error' - > $1
    fi
    exit 0
fi

لفهم ما يحدث بشكل صحيح في نصوص مثل هذا، نحتاج أولًا إلى تقديم بعض المفاهيم التي تظهر كثيرًا عندما تتواصل برامج مُعالج الأوامر مع بعضها البعض أو مع بيئة مُعالج الأوامر:

  • الوسائط (Arguments)
  • التدفقات (Streams)
  • متغيرات البيئة (Environment variables)
  • أكواد الإرجاع (Return codes)
  • الإشارات (Signals)

الوسائط (Arguments)

تستقبل برامج مُعالج الأوامر قائمة من الوسائط عند تنفيذها. الوسائط هي نصوص (Strings) بسيطة في مُعالج الأوامر، والأمر متروك للبرنامج لتفسيرها. على سبيل المثال، عندما ننفّذ ls -l folder/، فإننا نشغّل البرنامج /bin/ls بوسائط ['-l', 'folder/'].

من داخل نص مُعالج الأوامر، نصل إلى هذه الوسائط عبر صيغة خاصة. للوصول إلى الوسيطة الأولى نستخدم المتغير $1، والثانية $2 وهكذا حتى $9. وللوصول إلى جميع الوسائط كقائمة نستخدم $@، ولجلب عدد الوسائط $#. كما يمكننا أيضًا الوصول إلى اسم البرنامج عبر $0.

بالنسبة لمعظم البرامج، تتكون الوسائط من مزيج من العلامات (Flags) والنصوص العادية. يمكن التعرف على العلامات لأنها تسبق بشرطة (-) أو شرطتين (--). العلامات عادة ما تكون اختيارية ودورها تعديل سلوك البرنامج. على سبيل المثال، ls -l يغيّر طريقة تنسيق ls لمخرجاته.

سترى علامات بشرطتين بأسماء طويلة مثل --all، وعلامات بشرطة واحدة مثل -a، والتي غالبًا ما يتبعها حرف واحد. قد يتم تحديد نفس الخيار بالصيغتين، فـ ls -a و ls --all متكافئان. غالبًا ما تُجمَّع العلامات ذات الشرطة الواحدة، لذلك ls -l -a و ls -la متكافئان أيضًا. ترتيب العلامات عادةً لا يهم كذلك، فـ ls -la و ls -al يعطيان نفس النتيجة. بعض العلامات شائعة جدًا، وكلما أصبحت أكثر دراية ببيئة مُعالج الأوامر ستصل إليها بشكل بديهي، مثل (--help، --verbose، --version).

العلامات هي أول مثال جيد على اصطلاحات مُعالج الأوامر. لغة مُعالج الأوامر لا تتطلب أن يستخدم برنامجنا - أو -- بهذه الطريقة المعينة. لا شيء يمنعنا من كتابة برنامج بصيغة myprogram +myoption myfile، لكن ذلك سيؤدي إلى ارتباك لأن التوقّع هو أن نستخدم الشرطات. عمليًا، توفر معظم لغات البرمجة مكتبات لتحليل علامات CLI (مثل argparse في بايثون لتحليل الوسائط بصيغة الشرطات).

اصطلاح شائع آخر في برامج CLI هو أن تقبل البرامج عددًا متغيرًا من الوسائط من نفس النوع. عندما تُعطى الوسائط بهذه الطريقة، ينفّذ الأمر نفس العملية على كل واحدة منها.

mkdir src
mkdir docs
# is equivalent to
mkdir src docs

قد يبدو هذا التبسيط الاصطلاحي (Syntax Sugar) غير ضروري في البداية، لكنه يصبح قويًا جدًا عند دمجه مع التوسيع بأنماط المطابقة (Globbing). التوسيع بأنماط المطابقة أو ما يُسمى Globs هي أنماط خاصة يقوم مُعالج الأوامر بتوسيعها قبل استدعاء البرنامج.

لنفترض أننا نريد حذف جميع ملفات .py في المجلد الحالي دون استخدام العودية (Recursion). مما تعلمناه في المحاضرة السابقة يمكننا تحقيق ذلك بتنفيذ

for file in $(ls | grep -P '\.py$'); do
    rm "$file"
done

ولكن يمكننا استبدال ذلك بـ rm *.py فقط!

عندما نكتب rm *.py في الطرفية، لن يستدعي مُعالج الأوامر البرنامج /bin/rm بوسائط ['*.py']. بدلًا من ذلك، سيبحث مُعالج الأوامر عن الملفات في المجلد الحالي المطابقة للنمط *.py حيث يمكن لـ * مطابقة أي سلسلة مكونة من صفر أو أكثر من الأحرف من أي نوع. لذا إذا كان مجلدنا يحتوي على main.py و utils.py، فسيستقبل برنامج rm الوسائط ['main.py', 'utils.py'].

أكثر أنماط Globs شيوعًا ستجدها هي أحرف البدل * (صفر أو أكثر من أي شيء)، و ? (حرف واحد بالضبط من أي شيء)، والأقواس المتعرجة. الأقواس المتعرجة {} توسّع قائمة مفصولة بفواصل من الأنماط إلى وسائط متعددة.

عمليًا، تُفهم Globs بشكل أفضل مع أمثلة توضيحية.

touch folder/{a,b,c}.py
# Will expand to
touch folder/a.py folder/b.py folder/c.py

convert image.{png,jpg}
# Will expand to
convert image.png image.jpg

cp /path/to/project/{setup,build,deploy}.sh /newpath
# Will expand to
cp /path/to/project/setup.sh /path/to/project/build.sh /path/to/project/deploy.sh /newpath

# Globbing techniques can also be combined
mv *{.py,.sh} folder
# Will move all *.py and *.sh files

بعض مُعالجات الأوامر (مثل zsh) تدعم أشكالًا أكثر تقدمًا من Globbing مثل ** التي تتوسع لتشمل المسارات العودية. لذا فإن rm **/*.py سيحذف جميع ملفات .py بشكل عودي.

التدفقات (Streams)

عندما ننفّذ خط أنابيب (Pipeline) مثل

cat myfile | grep -P '\d+' | uniq -c

نرى أن برنامج grep يتواصل مع برنامجي cat و uniq.

ملاحظة مهمة هنا هي أن البرامج الثلاثة تُنفَّذ في وقت واحد. أي أن مُعالج الأوامر لا يستدعي cat أولًا ثم grep ثم uniq. بدلًا من ذلك، يتم إطلاق البرامج الثلاثة جميعًا ويقوم مُعالج الأوامر بربط مخرجات cat بمدخلات grep ومخرجات grep بمدخلات uniq. عند استخدام عامل الأنبوب |، يتعامل مُعالج الأوامر مع تدفقات من البيانات تتدفق من برنامج إلى التالي في السلسلة.

يمكننا إثبات هذا التزامن — جميع الأوامر في خط الأنابيب تبدأ فورًا:

$ (sleep 15 && cat numbers.txt) | grep -P '^\d$' | sort | uniq  &
[1] 12345
$ ps | grep -P '(sleep|cat|grep|sort|uniq)'
  32930 pts/1    00:00:00 sleep
  32931 pts/1    00:00:00 grep
  32932 pts/1    00:00:00 sort
  32933 pts/1    00:00:00 uniq
  32948 pts/1    00:00:00 grep

يمكننا أن نرى أن جميع العمليات باستثناء cat تعمل فورًا. يُنشئ مُعالج الأوامر جميع العمليات ويصل تدفقاتها قبل أن تنتهي أي منها. لن يبدأ cat إلا بعد انتهاء sleep، وسيُرسل مخرجات cat إلى grep وهكذا دواليك.

كل برنامج لديه تدفق إدخال، يُسمى stdin (للإدخال القياسي). عند استخدام الأنابيب، يتم توصيل stdin تلقائيًا. داخل النصوص، تقبل العديد من البرامج - كاسم ملف بمعنى "اقرأ من stdin":

# These are equivalent when data comes from a pipe
echo "hello" | grep "hello"
echo "hello" | grep "hello" -

بالمثل، كل برنامج لديه تدفقا إخراج: stdout و stderr. الإخراج القياسي (Standard Output) هو الأكثر شيوعًا، وهو المستخدم لتمرير إخراج البرنامج إلى الأمر التالي في خط الأنابيب. الخطأ القياسي (Standard Error) هو تدفق بديل مخصص للبرامج لتقرير التحذيرات وأنواع أخرى من المشكلات، دون أن يُحلَّل هذا الإخراج بواسطة الأمر التالي في السلسلة.

$ ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
$ ls /nonexistent | grep "pattern"
ls: cannot access '/nonexistent': No such file or directory
# The error message still appears because stderr is not piped
$ ls /nonexistent 2>/dev/null
# No output - stderr was redirected to /dev/null

يوفر مُعالج الأوامر صيغة لإعادة توجيه هذه التدفقات. إليك بعض الأمثلة التوضيحية.

# Redirect stdout to a file (overwrite)
echo "hello" > output.txt

# Redirect stdout to a file (append)
echo "world" >> output.txt

# Redirect stderr to a file
ls foobar 2> errors.txt

# Redirect both stdout and stderr to the same file
ls foobar &> all_output.txt

# Redirect stdin from a file
grep "pattern" < input.txt

# Discard output by redirecting to /dev/null
cmd > /dev/null 2>&1

أداة قوية أخرى تجسّد فلسفة يونكس هي fzf، وهي أداة بحث ضبابي (Fuzzy Finder). تقرأ الأسطر من stdin وتوفّر واجهة تفاعلية للتصفية والاختيار:

$ ls | fzf
$ cat ~/.bash_history | fzf

يمكن دمج fzf مع العديد من عمليات مُعالج الأوامر. سنرى المزيد من استخداماته عندما نتحدث عن تخصيص مُعالج الأوامر.

متغيرات البيئة (Environment Variables)

لتعيين متغيرات في bash نستخدم الصيغة foo=bar، ثم نصل إلى قيمة المتغير بصيغة $foo. لاحظ أن foo = bar صيغة غير صالحة لأن مُعالج الأوامر سيحلّلها كاستدعاء للبرنامج foo بوسائط ['=', 'bar']. في نصوص مُعالج الأوامر، دور الحرف المسافة هو تنفيذ تقسيم الوسائط (Argument Splitting). قد يكون هذا السلوك محيرًا ويصعب التعود عليه، لذا ضعه في اعتبارك.

متغيرات مُعالج الأوامر ليس لها أنواع، فهي جميعًا نصوص (Strings). لاحظ أنه عند كتابة تعبيرات نصية في مُعالج الأوامر، فإن الاقتباسات المفردة والمزدوجة ليست قابلة للتبادل. النصوص المحددة بـ ' هي نصوص حرفية ولن توسّع المتغيرات، أو تنفّذ استبدال الأوامر، أو تعالج تسلسلات الهروب، بينما النصوص المحددة بـ " ستفعل ذلك.

foo=bar
echo "$foo"
# prints bar
echo '$foo'
# prints $foo

لالتقاط مخرجات أمر في متغير نستخدم استبدال الأوامر (Command Substitution). عندما ننفّذ

files=$(ls)
echo "$files" | grep README
echo "$files" | grep ".py"

يتم وضع الإخراج (تحديدًا stdout) لأمر ls في المتغير $files الذي يمكننا الوصول إليه لاحقًا. محتوى المتغير $files يتضمن أسطر الأسطر الجديدة (Newlines) من إخراج ls، وهذا ما يسمح لبرامج مثل grep بالعمل على كل عنصر على حدة.

ميزة مشابهة أقل شهرة هي استبدال العمليات (Process Substitution)؛ فـ <( CMD ) ستنفّذ CMD وتضع الإخراج في ملف مؤقت وتستبدل () بإسم ذلك الملف. هذا مفيد عندما تتوقع الأوامر أن تُمرَّر القيم عبر ملف بدلًا من STDIN. على سبيل المثال، diff <(ls src) <(ls docs) سيُظهر الفروقات بين الملفات في المجلدين src و docs.

عندما يستدعي برنامج في مُعالج الأوامر برنامجًا آخر، فإنه يمرّر مجموعة من المتغيرات يُشار إليها غالبًا باسم متغيرات البيئة (Environment Variables). من داخل مُعالج الأوامر يمكننا إيجاد متغيرات البيئة الحالية بتشغيل printenv. لتمرير متغير بيئة بشكل صريح، يمكننا أن نُسبق الأمر بتعيين متغير

تُكتب متغيرات البيئة تقليديًا بالأحرف الكبيرة (مثل HOME، PATH، DEBUG). هذا اصطلاح وليس متطلبًا تقنيًا، لكن اتباعه يساعد على التمييز بين متغيرات البيئة ومتغيرات مُعالج الأوامر المحلية التي عادةً ما تكون بالأحرف الصغيرة.

TZ=Asia/Tokyo date  # prints the current time in Tokyo
echo $TZ  # this will be empty, since TZ was only set for the child command

بدلًا من ذلك، يمكننا استخدام الدالة المدمجة export التي ستعدّل بيئتنا الحالية وبالتالي سترث جميع العمليات الفرعية المتغير:

export DEBUG=1
# All programs from this point onwards will have DEBUG=1 in their environment
bash -c 'echo $DEBUG'
# prints 1

لحذف متغير استخدم الأمر المدمج unset، مثل unset DEBUG.

متغيرات البيئة هي اصطلاح آخر من اصطلاحات مُعالج الأوامر. يمكن استخدامها لتعديل سلوك العديد من البرامج بشكل ضمني بدلًا من الصريح. على سبيل المثال، يضع مُعالج الأوامر متغير البيئة $HOME بمسار مجلد الصفحة الرئيسية للمستخدم الحالي. ثم تستطيع البرامج الوصول إلى هذا المتغير للحصول على هذه المعلومة بدلًا من طلب --home /home/alice صراحةً. مثال شائع آخر هو $TZ، الذي تستخدمه برامج كثيرة لتنسيق التواريخ والأوقات وفقًا للمنطقة الزمنية المحددة.

أكواد الإرجاع (Return Codes)

كما رأينا سابقًا، يتم نقل الإخراج الرئيسي لبرنامج مُعالج الأوامر عبر تدفقي stdout/stderr وآثار جانبية على نظام الملفات.

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

يمتلك مُعالج الأوامر معاملات منطقية && و || لتنفيذ عمليات AND و OR على التوالي. على عكس تلك الموجودة في لغات البرمجة العادية، تعمل هذه المعاملات في مُعالج الأوامر على كود الإرجاع للبرامج. كلا المعاملين قصيرا الدائرة (Short-circuiting). هذا يعني أنه يمكن استخدامهما لتشغيل الأوامر شرطيًا بناءً على نجاح أو فشل الأوامر السابقة، حيث يُحدَّد النجاح بناءً على ما إذا كان كود الإرجاع صفرًا أم لا. بعض الأمثلة:

# echo will only run if grep succeeds (finds a match)
grep -q "pattern" file.txt && echo "Pattern found"

# echo will only run if grep fails (no match)
grep -q "pattern" file.txt || echo "Pattern not found"

# true is a shell program that always succeeds
true && echo "This will always print"

# and false is a shell program that always fails
false || echo "This will always print"

نفس المبدأ ينطبق على عبارات if و while، فكلاهما يستخدم أكواد الإرجاع لاتخاذ القرارات:

# if uses the return code of the condition command (0 = true, nonzero = false)
if grep -q "pattern" file.txt; then
    echo "Found"
fi

# while loops continue as long as the command returns 0
while read line; do
    echo "$line"
done < file.txt

الإشارات (Signals)

في بعض الحالات ستحتاج إلى مقاطعة برنامج أثناء تنفيذه، على سبيل المثال إذا كان الأمر يستغرق وقتًا طويلًا لإكماله. أبسط طريقة لمقاطعة برنامج هي الضغط على Ctrl-C وسيتوقف الأمر غالبًا. ولكن كيف يعمل هذا فعليًا ولماذا يفشل أحيانًا في إيقاف العملية؟

$ sleep 100
^C
$

لاحظ أن ^C هنا هو الشكل الذي يُعرض به Ctrl-C عند كتابته في الطرفية.

تحت الغطاء، إليك ما حدث هنا:

  1. ضغطنا على Ctrl-C
  2. تعرّف مُعالج الأوامر على التركيبة الخاصة من الأحرف
  3. أرسلت عملية مُعالج الأوامر إشارة SIGINT إلى عملية sleep
  4. قاطعت الإشارة تنفيذ عملية sleep

الإشارات هي آلية اتصال خاصة. عندما تستقبل عملية إشارة، فإنها توقف تنفيذها وتتعامل مع الإشارة وربما تغيّر مسار التنفيذ بناءً على المعلومات التي وصلت بها الإشارة. لهذا السبب، الإشارات هي مقاطعات برمجية (Software Interrupts).

في حالتنا، عند كتابة Ctrl-C يدفع هذا مُعالج الأوامر إلى تسليم إشارة SIGINT للعملية. إليك مثالًا بسيطًا على برنامج بايثون يلتقط SIGINT ويتجاهلها، فلا يتوقف بعدها. لإيقاف هذا البرنامج يمكننا الآن استخدام إشارة SIGQUIT بدلًا منها، بكتابة Ctrl-\.

#!/usr/bin/env python
import signal, time

def handler(signum, time):
    print("\nI got a SIGINT, but I am not stopping")

signal.signal(signal.SIGINT, handler)
i = 0
while True:
    time.sleep(.1)
    print("\r{}".format(i), end="")
    i += 1

إليك ما يحدث إذا أرسلنا SIGINT مرتين لهذا البرنامج، متبوعًا بـ SIGQUIT. لاحظ أن ^ هو الشكل الذي يُعرض به Ctrl عند كتابته في الطرفية.

$ python sigint.py
24^C
I got a SIGINT, but I am not stopping
26^C
I got a SIGINT, but I am not stopping
30^\[1]    39913 quit       python sigint.py

بينما ترتبط كل من SIGINT و SIGQUIT عادةً بطلبات متعلقة بالطرفية، فإن الإشارة الأكثر عمومية لطلب إنهاء عملية بشكل متزن (Gracefully) هي إشارة SIGTERM. لإرسال هذه الإشارة يمكننا استخدام أمر kill، بالصيغة kill -TERM <PID>.

يمكن للإشارات أن تفعل أشياء أخرى تتجاوز إيقاف عملية. على سبيل المثال، SIGSTOP تُوقِف عملية مؤقتًا. في الطرفية، كتابة Ctrl-Z ستُدفع مُعالج الأوامر لإرسال إشارة SIGTSTP، اختصارًا لـ Terminal Stop (أي نسخة الطرفية من SIGSTOP).

يمكننا بعد ذلك متابعة المهمة المتوقفة في المقدمة (Foreground) أو في الخلفية (Background) باستخدام fg أو bg، على التوالي.

أمر jobs يعرض قائمة المهام غير المكتملة المرتبطة بجلسة الطرفية الحالية. يمكنك الإشارة إلى هذه المهام باستخدام pid الخاص بها (يمكنك استخدام pgrep لمعرفة ذلك). بشكل أكثر بديهية، يمكنك أيضًا الإشارة إلى عملية باستخدام علامة النسبة المئوية متبوعة برقم المهمة (المعروض بواسطة jobs). للإشارة إلى آخر مهمة تم نقلها للخلفية يمكنك استخدام المعامل الخاص $!.

أمر آخر يجب معرفته هو أن اللاحقة & في أمر ما ستشغّله في الخلفية، معيدًا لك المطالبة (Prompt)، على الرغم من أنها ستستخدم STDOUT الخاص بمُعالج الأوامر وهو ما قد يكون مزعجًا (استخدم إعادة التوجيه في هذه الحالة). وبالمثل، لنقل برنامج يعمل بالفعل إلى الخلفية يمكنك تنفيذ Ctrl-Z متبوعًا بـ bg.

لاحظ أن العمليات في الخلفية ما زالت عمليات فرعية للطرفية الخاصة بك وستموت إذا أغلقت الطرفية (وهذا سيرسل إشارة أخرى، SIGHUP). لمنع ذلك يمكنك تشغيل البرنامج مع nohup (وهو غلاف يتجاهل SIGHUP)، أو استخدام disown إذا كانت العملية قد بدأت بالفعل. بدلًا من ذلك، يمكنك استخدام مُضاعِف الطرفية (Terminal Multiplexer) كما سنرى في القسم التالي.

إليك جلسة نموذجية لعرض بعض هذه المفاهيم.

$ sleep 1000
^Z
[1]  + 18653 suspended  sleep 1000

$ nohup sleep 2000 &
[2] 18745
appending output to nohup.out

$ jobs
[1]  + suspended  sleep 1000
[2]  - running    nohup sleep 2000

$ kill -SIGHUP %1
[1]  + 18653 hangup     sleep 1000

$ kill -SIGHUP %2   # nohup protects from SIGHUP

$ jobs
[2]  + running    nohup sleep 2000

$ kill %2
[2]  + 18745 terminated  nohup sleep 2000

إشارة خاصة هي SIGKILL لأنها لا يمكن التقاطها بواسطة العملية وستنهيها دائمًا فورًا. ومع ذلك، يمكن أن يكون لها آثار جانبية سيئة مثل ترك عمليات فرعية يتيمة (Orphaned).

يمكنك معرفة المزيد عن هذه الإشارات وغيرها هنا أو بكتابة man signal أو kill -l.

داخل نصوص مُعالج الأوامر، يمكنك استخدام الأداة المدمجة trap لتنفيذ أوامر عند استلام الإشارات. هذا مفيد لعمليات التنظيف:

#!/usr/bin/env bash
cleanup() {
    echo "Cleaning up temporary files..."
    rm -f /tmp/mytemp.*
}
trap cleanup EXIT  # Run cleanup when script exits
trap cleanup SIGINT SIGTERM  # Also on Ctrl-C or kill

الأجهزة البعيدة (Remote Machines)

أصبح من الشائع أكثر فأكثر أن يعمل المبرمجون مع الخوادم البعيدة (Remote Servers) في عملهم اليومي. الأداة الأكثر شيوعًا لهذا الغرض هي SSH (اختصارًا لـ Secure Shell - غلاف آمن) التي ستساعدنا على الاتصال بخادم بعيد وتوفير واجهة مُعالج الأوامر المألوفة الآن. نتصل بالخادم بأمر مثل:

ssh alice@server.mit.edu

هنا نحاول الاتصال عبر ssh كمستخدم alice في الخادم server.mit.edu.

ميزة تُغفل غالبًا لأمر ssh هي القدرة على تشغيل الأوامر دون تفاعلية. يتعامل ssh بشكل صحيح مع إرسال stdin واستقبال stdout للأمر، لذا يمكننا دمجه مع أوامر أخرى

# here ls runs in the remote, and wc runs locally
ssh alice@server ls | wc -l

# here both ls and wc run in the server
ssh alice@server 'ls | wc -l'

جرّب تثبيت Mosh كبديل لـ SSH يتعامل مع انقطاع الاتصالات، والدخول/الخروج من وضع السكون (Sleep)، وتغيير الشبكات، والتعامل مع الروابط عالية التأخير (Latency).

لكي يسمح لنا ssh بتشغيل أوامر في الخادم البعيد، نحتاج إلى إثبات أننا مصرح لنا بذلك. يمكننا القيام بذلك عبر كلمات المرور أو مفاتيح ssh. المصادقة بالمفاتيح (Key-based Authentication) تستخدم تشفير المفتاح العام (Public-key Cryptography) لإثبات للخادم أن العميل يملك المفتاح الخاص السري دون كشف المفتاح. المصادقة بالمفاتيح هي أكثر ملاءمة وأكثر أمانًا، لذا يجب أن تفضّلها. لاحظ أن المفتاح الخاص (غالبًا ~/.ssh/id_rsa ومؤخرًا ~/.ssh/id_ed25519) هو فعليًا كلمة مرورك، فعاملها كذلك ولا تشارك محتواه أبدًا.

لتوليد زوج من المفاتيح يمكنك تشغيل ssh-keygen.

ssh-keygen -a 100 -t ed25519 -f ~/.ssh/id_ed25519

إذا قمت يومًا بتكوين الدفع (Push) إلى GitHub باستخدام مفاتيح SSH، فمن المحتمل أنك أنجزت الخطوات الموضحة هنا ولديك بالفعل زوج مفاتيح صالح. للتحقق مما إذا كانت لديك عبارة سرية (Passphrase) والتحقق منها يمكنك تشغيل ssh-keygen -y -f /path/to/key.

في جانب الخادم، سيبحث ssh في .ssh/authorized_keys لتحديد أي العملاء يجب أن يدخل. لنسخ المفتاح العام يمكنك استخدام:

cat .ssh/id_ed25519.pub | ssh alice@remote 'cat >> ~/.ssh/authorized_keys'

# or more simply (if ssh-copy-id is available)

ssh-copy-id -i .ssh/id_ed25519 alice@remote

إلى جانب تشغيل الأوامر، يمكن استخدام الاتصال الذي ينشئه ssh لنقل الملفات من وإلى الخادم بأمان. scp هو الأداة الأكثر تقليدية وصيغته هي scp path/to/local_file remote_host:path/to/remote_file. يحسّن rsync على scp باكتشاف الملفات المتطابقة محليًا وعن بُعد، ومنع نسخها مرة أخرى. كما يوفر تحكمًا أدق في الروابط الرمزية (Symlinks) والأذونات وله ميزات إضافية مثل العلامة --partial التي يمكنها الاستئناف من نسخة متقطعة سابقًا. rsync له صيغة مشابهة لـ scp.

توجد إعدادات عميل SSH في ~/.ssh/config وتتيح لنا تعريف المضيفات (Hosts) وضبط الإعدادات الافتراضية لها. هذا الملف الإعدادي لا يُقرأ بواسطة ssh فحسب بل أيضًا بواسطة برامج أخرى مثل scp و rsync و mosh وغيرها.

Host vm
    User alice
    HostName 172.16.174.141
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

# Configs can also take wildcards
Host *.mit.edu
    User alice

مُضاعِفات الطرفية (Terminal Multiplexers)

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

مُضاعِفات الطرفية مثل tmux تتيح لك مضاعفة نوافذ الطرفية باستخدام المقاطع (Panes) والتبويبات (Tabs) لتتفاعل مع جلسات مُعالج أوامر متعددة بكفاءة. علاوة على ذلك، تتيح لك مُضاعِفات الطرفية فصل (Detach) جلسة الطرفية الحالية وإعادة الالتحاق (Reattach) بها في وقت لاحق. بسبب ذلك، تُعد مُضاعِفات الطرفية مريحة جدًا عند العمل مع الأجهزة البعيدة، إذ تتجنب الحاجة لاستخدام nohup وحيل مماثلة.

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

tmux يتوقع أن تعرف اختصارات المفاتيح الخاصة به، وجميعها بالصيغة <C-b> x حيث يعني ذلك (1) اضغط Ctrl+b، (2) حرّر Ctrl+b، ثم (3) اضغط x. tmux لديه التسلسل التالي من الكائنات:

  • الجلسات (Sessions) - الجلسة هي مساحة عمل مستقلة تحتوي على نافذة واحدة أو أكثر

    • tmux يبدأ جلسة جديدة.
    • tmux new -s NAME يبدأها بهذا الاسم.
    • tmux ls يعرض الجلسات الحالية
    • داخل tmux كتابة <C-b> d تفصل الجلسة الحالية
    • tmux a يلتحق بآخر جلسة. يمكنك استخدام العلامة -t لتحديد أيها
  • النوافذ (Windows) - مكافئة للتبويبات في المحررات أو المتصفحات، وهي أجزاء منفصلة بصريًا من نفس الجلسة

    • <C-b> c ينشئ نافذة جديدة. لإغلاقها يمكنك ببساطة إنهاء الأغلفة بتنفيذ <C-d>
    • <C-b> N ينتقل إلى النافذة N ث. لاحظ أنها مرقمة
    • <C-b> p يذهب إلى النافذة السابقة
    • <C-b> n يذهب إلى النافذة التالية
    • <C-b> , يعيد تسمية النافذة الحالية
    • <C-b> w يعرض النوافذ الحالية
  • المقاطع (Panes) - مثل تقسيمات vim، تتيح لك المقاطع تشغيل عدة أغلفة في نفس العرض المرئي.

    • <C-b> " يقسم المقطع الحالي أفقيًا
    • <C-b> % يقسم المقطع الحالي رأسيًا
    • <C-b> <direction> ينتقل إلى المقطع في الاتجاه المحدد. الاتجاه هنا يعني مفاتيح الأسهم.
    • <C-b> z يبدّل التكبير (Zoom) للمقطع الحالي
    • <C-b> [ يبدأ التمرير الخلفي (Scrollback). يمكنك بعدها الضغط على <space> لبدء تحديد و <enter> لنسخ ذلك التحديد.
    • <C-b> <space> يتنقل عبر ترتيبات المقاطع.

لتعلم المزيد عن tmux، فكّر في قراءة هذا الدرس السريع وهذا الشرح الأكثر تفصيلًا.

بامتلاك tmux و SSH في حقيبة أدواتك، سترغب في جعل بيئتك تبدو وكأنها وطن لك على أي جهاز. وهنا يأتي دور تخصيص مُعالج الأوامر.

تخصيص مُعالج الأوامر (Customizing the Shell)

يتم تكوين مجموعة واسعة من برامج سطر الأوامر باستخدام ملفات نصية عادية تُعرف باسم ملفات النقاط (Dotfiles) (لأن أسماء الملفات تبدأ بنقطة .، مثل ~/.vimrc، بحيث تكون مخفية في قائمة ls افتراضيًا).

ملفات النقاط هي اصطلاح آخر من اصطلاحات مُعالج الأوامر. النقطة في المقدمة الغرض منها "إخفاؤها" عند السرد (نعم، اصطلاح آخر).

مُعالجات الأوامر هي مثال واحد على برامج تُكوَّن بمثل هذه الملفات. عند الإقلاع، سيقوم مُعالج الأوامر بقراءة العديد من الملفات لتحميل إعداداته. اعتمادًا على مُعالج الأوامر وما إذا كنت تبدأ جلسة تسجيل دخول (Login) و/أو جلسة تفاعلية (Interactive)، قد تكون العملية بأكملها معقدة جدًا. هنا مصدر ممتاز حول هذا الموضوع.

بالنسبة لـ bash، فإن تعديل .bashrc أو .bash_profile سيعمل في معظم الأنظمة. بعض الأمثلة الأخرى للأدوات التي يمكن تكوينها عبر ملفات النقاط:

  • bash - ~/.bashrc، ~/.bash_profile
  • git - ~/.gitconfig
  • vim - ~/.vimrc ومجلد ~/.vim
  • ssh - ~/.ssh/config
  • tmux - ~/.tmux.conf

تغيير شائع في التكوين هو إضافة مواقع جديدة ليبحث فيها مُعالج الأوامر عن البرامج. ستواجه هذا النمط عند تثبيت البرمجيات:

export PATH="$PATH:path/to/append"

هنا، نخبر مُعالج الأوامر بتعيين قيمة المتغير $PATH إلى قيمته الحالية بالإضافة إلى مسار جديد، وجعل جميع العمليات الفرعية ترث هذه القيمة الجديدة لـ PATH. سيسمح هذا للعمليات الفرعية بإيجاد البرامج الموجودة تحت path/to/append.

تخصيص مُعالج الأوامر غالبًا يعني تثبيت أدوات سطر أوامر جديدة. تُسهل مديرو الحزم (Package Managers) ذلك. فهم يتعاملون مع تنزيل وتثبيت وتحديث البرمجيات. أنظمة التشغيل المختلفة لديها مديرو حزم مختلفون: macOS يستخدم Homebrew و Ubuntu/Debian يستخدمان apt و Fedora يستخدم dnf و Arch يستخدم pacman. سنغطي مديري الحزم بمزيد من العمق في محاضرة شحن الكود (Shipping Code).

إليك كيفية تثبيت أداتين مفيدتين باستخدام Homebrew على macOS:

# ripgrep: a faster grep with better defaults
brew install ripgrep

# fd: a faster, user-friendly find
brew install fd

مع تثبيت هذه، يمكنك استخدام rg بدلًا من grep و fd بدلًا من find.

تحذير بشأن curl | bash: سترى غالبًا تعليمات تثبيت مثل curl -fsSL https://example.com/install.sh | bash. هذا النمط ينزّل نصًا وينفّذه فورًا، وهو أمر مريح لكنه محفوف بالمخاطر؛ فأنت تشغّل كودًا لم تفحصه. النهج الأكثر أمانًا هو التنزيل أولًا ثم المراجعة ثم التنفيذ:

curl -fsSL https://example.com/install.sh -o install.sh
less install.sh  # review the script
bash install.sh

بعض برامج التثبيت تستخدم صيغة مختلفة أكثر أمانًا قليلًا: /bin/bash -c "$(curl -fsSL https://url)" التي تضمن على الأقل أن bash هو من يفسّر النص بدلًا من مُعالج الأوامر الحالي.

عندما تحاول تشغيل أمر غير مثبت، سيُظهر مُعالج الأوامر command not found. موقع command-not-found.com مورد مفيد يمكنك استخدامه للبحث عن أي أمر لمعرفة كيفية تثبيته عبر مديري الحزم والتوزيعات المختلفة.

أداة مفيدة أخرى هي tldr التي توفّر صفحات دليل (Man Pages) مبسطة تركز على الأمثلة. بدلًا من قراءة وثائق طويلة، يمكنك رؤية أنماط الاستخدام الشائعة بسرعة:

$ tldr fd
  An alternative to find.
  Aims to be faster and easier to use than find.

  Recursively find files matching a pattern in the current directory:
      fd "pattern"

  Find files that begin with "foo":
      fd "^foo"

  Find files with a specific extension:
      fd --extension txt

أحيانًا لا تحتاج إلى برنامج جديد كامل، بل مجرد اختصار لأمر موجود بعلامات معينة. وهنا تأتي الأسماء المستعارة (Aliases).

يمكننا أيضًا إنشاء أسماء مستعارة خاصة بنا باستخدام الأداة المدمجة alias. الاسم المستعار في مُعالج الأوامر هو صيغة قصيرة لأمر آخر سيستبدله مُعالج الأوامر تلقائيًا قبل تقييم التعبير. على سبيل المثال، للاسم المستعار في bash البنية التالية:

alias alias_name="command_to_alias arg1 arg2"

لاحظ أنه لا توجد مسافة حول علامة المساواة =، لأن alias أمر مُعالج أوامر يأخذ وسيطة واحدة.

الأسماء المستعارة لديها ميزات مريحة عديدة:

# Make shorthands for common flags
alias ll="ls -lh"

# Save a lot of typing for common commands
alias gs="git status"
alias gc="git commit"

# Save you from mistyping
alias sl=ls

# Overwrite existing commands for better defaults
alias mv="mv -i"           # -i prompts before overwrite
alias mkdir="mkdir -p"     # -p make parent dirs as needed
alias df="df -h"           # -h prints human readable format

# Alias can be composed
alias la="ls -A"
alias lla="la -l"

# To ignore an alias run it prepended with \
\ls
# Or disable an alias altogether with unalias
unalias la

# To get an alias definition just call it with alias
alias ll
# Will print ll='ls -lh'

الأسماء المستعارة لها قيود: لا يمكنها أخذ وسائط في منتصف الأمر. للسلوك الأكثر تعقيدًا، يجب استخدام دوال مُعالج الأوامر (Shell Functions) بدلًا منها.

معظم مُعالجات الأوامر تدعم Ctrl-R للبحث العكسي في السجل (Reverse History Search). اضغط Ctrl-R وابدأ الكتابة للبحث في الأوامر السابقة. قدّمنا سابقًا fzf كأداة بحث ضبابي؛ مع تكوين دمج fzf مع مُعالج الأوامر، يصبح Ctrl-R بحثًا ضبابيًا تفاعليًا عبر سجلك بالكامل، أقوى بكثير من الافتراضي.

كيف يجب أن تنظم ملفات النقاط الخاصة بك؟ يجب أن تكون في مجلد خاص بها، تحت نظام التحكم بالنسخ (Version Control)، ومرتبطة بصلات رمزية (Symlinked) في مكانها باستخدام نص. وهذا له فوائد:

  • تثبيت سهل: إذا سجلت الدخول إلى جهاز جديد، فإن تطبيق تخصيصاتك سيستغرق دقيقة واحدة فقط.
  • قابلية النقل (Portability): أدواتك ستعمل بنفس الطريقة في كل مكان.
  • المزامنة (Synchronization): يمكنك تحديث ملفات النقاط الخاصة بك في أي مكان وإبقائها متزامنة كلها.
  • تتبع التغييرات: من المحتمل أنك ستحافظ على ملفات النقاط الخاصة بك طوال مسيرتك البرمجية، والسجل الإصدارات أمر لطيف وجوده للمشاريع طويلة العمر.

ماذا يجب أن تضع في ملفات النقاط الخاصة بك؟ يمكنك التعرف على إعدادات أداتك بقراءة الوثائق عبر الإنترنت أو صفحات الرجل (Man Pages). طريقة رائعة أخرى هي البحث في الإنترنت عن منشورات المدونات حول برامج محددة، حيث يخبرك المؤلفون عن تخصيصاتهم المفضلة. هناك طريقة أخرى للتعرف على التخصيصات وهي الاطلاع على ملفات النقاط الخاصة بأشخاص آخرين: يمكنك العثور على الكثير من مستودعات ملفات النقاط على GitHub — انظر الأكثر شهرة هنا (ننصحك بعدم نسخ الإعدادات بشكل أعمى). هنا مورد جيد آخر حول هذا الموضوع.

جميع محاضري المقرر لديهم ملفات نقاط متاحة علنًا على GitHub: Anish، Jon، Jose.

الأطر والمكونات الإضافية (Frameworks and plugins) يمكنها تحسين مُعالج الأوامر أيضًا. بعض الأطر العامة الشائعة هي prezto أو oh-my-zsh والمكونات الأصغر التي تركز على ميزات محددة:

مُعالجات أوامر مثل fish تتضمن العديد من هذه الميزات افتراضيًا.

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

الذكاء الاصطناعي في مُعالج الأوامر (AI in the Shell)

هناك طرق عديدة لدمج أدوات الذكاء الاصطناعي في مُعالج الأوامر. إليك بعض الأمثلة على مستويات مختلفة من التكامل:

توليد الأوامر (Command generation): أدوات مثل simonw/llm يمكنها المساعدة في توليد أوامر مُعالج الأوامر من أوصاف اللغة الطبيعية:

$ llm cmd "find all python files modified in the last week"
find . -name "*.py" -mtime -7

تكامل خط الأنابيب: يمكن دمج نماذج اللغة الكبيرة (LLMs) في خطوط أنابيب مُعالج الأوامر لمعالجة وتحويل البيانات. تكون مفيدة بشكل خاص عندما تحتاج إلى استخراج معلومات من صيغ غير متسقة حيث يكون التعبير النمطي (Regex) مؤلمًا:

$ cat users.txt
Contact: john.doe@example.com
User 'alice_smith' logged in at 3pm
Posted by: @bob_jones on Twitter
Author: Jane Doe (jdoe)
Message from mike_wilson yesterday
Submitted by user: sarah.connor
$ INSTRUCTIONS="Extract just the username from each line, one per line, nothing else"
$ llm "$INSTRUCTIONS" < users.txt
john.doe
alice_smith
bob_jones
jdoe
mike_wilson
sarah.connor

لاحظ كيف نستخدم "$INSTRUCTIONS" (مقتبسًا) لأن المتغير يحتوي على مسافات، و < users.txt لإعادة توجيه محتوى الملف إلى stdin.

أغلفة الذكاء الاصطناعي (AI shells): أدوات مثل Claude Code تعمل كغلاف فوقي (Meta-shell) يقبل أوامر باللغة الإنجليزية ويترجمها إلى عمليات مُعالج أوامر وتعديلات ملفات ومهام متعددة الخطوات أكثر تعقيدًا.

المُحاكيات الطرفية (Terminal Emulators)

إلى جانب تخصيص مُعالج الأوامر، يستحق الأمر قضاء بعض الوقت في تحديد اختيارك لـ المُحاكي الطرفي (Terminal Emulator) وإعداداته. المُحاكي الطرفي هو برنامج واجهة رسومية (GUI) يوفّر الواجهة النصية التي يعمل فيها مُعالج الأوامر. هناك العديد من المُحاكيات الطرفية.

بما أنك قد تقضي مئات إلى آلاف الساعات في الطرفية الخاصة بك، فمن المجدي النظر في إعداداتها. بعض الجوانب التي قد ترغب في تعديلها في الطرفية تتضمن:

  • اختيار الخط (Font)
  • نظام الألوان (Color Scheme)
  • اختصارات لوحة المفاتيح
  • دعم التبويبات/المقاطع (Tab/Pane support)
  • إعداد التمرير الخلفي (Scrollback configuration)
  • الأداء (بعض الطرفيات الأحدث مثل Alacritty أو Ghostty توفّر تسريع وحدة معالجة الرسوميات GPU).

تمارين (Exercises)

الوسائط والتوسيع بأنماط المطابقة (Arguments and Globs)

  1. قد ترى أوامر مثل cmd --flag -- --notaflag. الوسيطة -- هي وسيطة خاصة تخبر البرنامج بالتوقف عن تحليل العلامات. كل ما بعد -- يُعامل كوسيطة موضعية (Positional Argument). لماذا قد يكون هذا مفيدًا؟ جرّب تشغيل touch -- -myfile ثم إزالته دون --.

  2. اقرأ man ls واكتب أمر ls بعرض الملفات بالتالي:

    • يشمل جميع الملفات، بما فيها المخفية
    • تُعرض الأحجام بصيغة مقروءة للبشر (مثل 454M بدلًا من 454279954)
    • تُرتب الملفات حسب الأحدث
    • الإخراج ملون

    عينة من الإخراج ستبدو هكذا:

    -rw-r--r--   1 user group 1.1M Jan 14 09:53 baz
    drwxr-xr-x   5 user group  160 Jan 14 09:53 .
    -rw-r--r--   1 user group  514 Jan 14 06:42 bar
    -rw-r--r--   1 user group 106M Jan 13 12:12 foo
    drwx------+ 47 user group 1.5K Jan 12 18:08 ..
    
  3. استبدال العمليات <(command) يتيح لك استخدام إخراج أمر ما كما لو كان ملفًا. استخدم diff مع استبدال العمليات لمقارنة إخراج printenv و export. لماذا يختلفان؟ (تلميح: جرّب diff <(printenv | sort) <(export | sort)).

متغيرات البيئة (Environment Variables)

  1. اكتب دالتي bash marco و polo تقومان بالتالي: كلما نفّذت marco يجب حفظ مجلد العمل الحالي بطريقة ما، ثم عندما تنفّذ polo، أينما كنت في أي مجلد، يجب أن تعيد polo توجيهك بـ cd إلى المجلد الذي نفّذت فيه marco. لسهولة التصحيح يمكنك كتابة الكود في ملف marco.sh وإعادة (تحميل) التعريفات إلى مُعالج الأوامر بتنفيذ source marco.sh.

أكواد الإرجاع (Return Codes)

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

    #!/usr/bin/env bash
    
    n=$(( RANDOM % 100 ))
    
    if [[ $n -eq 42 ]]; then
       echo "Something went wrong"
       >&2 echo "The error was using magic numbers"
       exit 1
    fi
    
    echo "Everything went according to plan"
    

الإشارات والتحكم بالمهام (Signals and Job Control)

  1. ابدأ مهمة sleep 10000 في طرفية، انقلها للخلفية بـ Ctrl-Z واستمر في تنفيذها بـ bg. الآن استخدم pgrep لإيجاد pid الخاص بها و pkill لقتلها دون كتابة pid نفسه أبدًا. (تلميح: استخدم العلامتين -lf).

  2. لنفترض أنك لا تريد بدء عملية حتى تكتمل أخرى. كيف يمكنك تحقيق ذلك؟ في هذا التمرين، ستكون عمليتنا المحدودة دائمًا sleep 60 &. إحدى طرق تحقيق ذلك هي استخدام أمر wait. جرّب إطلاق أمر sleep وجعل أمر ls ينتظر حتى تنتهي العملية الخلفية.

    ومع ذلك، ستفشل هذه الاستراتيجية إذا بدأنا في جلسة bash مختلفة، لأن wait يعمل فقط مع العمليات الفرعية. ميزة لم نناقشها في الملاحظات هي أن حالة خروج أمر kill ستكون صفرًا عند النجاح وغير صفرية بخلاف ذلك. kill -0 لا يرسل إشارة لكنه سيعطي حالة خروج غير صفرية إذا لم تكن العملية موجودة. اكتب دالة bash باسم pidwait تأخذ pid وتنتظر حتى تكتمل العملية المعطاة. يجب أن تستخدم sleep لتجنب إهدار CPU دون داعٍ.

الملفات والأذونات (Files and Permissions)

  1. (متقدم) اكتب أمرًا أو نصًا لإيجاد الملف المُعدَّل مؤخرًا في مجلد بشكل عودي. بشكل أعم، هل يمكنك سرد جميع الملفات حسب الأحدث؟

مُضاعِفات الطرفية (Terminal Multiplexers)

  1. تابع درس tmux هذا الدرس التعليمي ثم تعلّم كيفية القيام ببعض التخصيصات الأساسية باتباع هذه الخطوات.

الأسماء المستعارة وملفات النقاط (Aliases and Dotfiles)

  1. أنشئ اسمًا مستعارًا dc يحل إلى cd لأوقات كتابته بالخطأ.

  2. شغّل history | awk '{$1="";print substr($0,2)}' | sort | uniq -c | sort -n | tail -n 10 للحصول على أوامرك العشرة الأكثر استخدامًا وفكّر في كتابة أسماء مستعارة أقصر لها. ملاحظة: هذا يعمل مع Bash؛ إذا كنت تستخدم ZSH فاستخدم history 1 بدلًا من history فقط.

  3. أنشئ مجلدًا لملفات النقاط الخاصة بك وأعد ضبط التحكم بالنسخ (Version Control).

  4. أضِف إعدادًا لبرنامج واحد على الأقل، مثل مُعالج الأوامر، مع بعض التخصيص (للبدء، يمكن أن يكون شيئًا بسيطًا مثل تخصيص مطالبة مُعالج الأوامر بضبط $PS1).

  5. أعد استعداد طريقة لتثبيت ملفات النقاط الخاصة بك بسرعة (ودون مجهود يدوي) على جهاز جديد. يمكن أن يكون هذا ببساطة نصًا يستدعي ln -s لكل ملف، أو يمكنك استخدام أداة متخصصة.

  6. اختبر نص التثبيت الخاص بك على آلة افتراضية جديدة.

  7. رحّل جميع إعدادات أدواتك الحالية إلى مستودع ملفات النقاط الخاص بك.

  8. انشر ملفات النقاط الخاصة بك على GitHub.

الأجهزة البعيدة (SSH)

ثبّت آلة افتراضية (Virtual Machine) لينكس (أو استخدم واحدة موجودة بالفعل) لهذه التمارين. إذا لم تكن على دراية بالآلات الافتراضية، فراجع هذا الدروس التعليمي لتثبيت واحدة.

  1. انتقل إلى ~/.ssh/ وتحقق مما إذا كان لديك زوج من مفاتيح SSH هناك. إذا لم يكن كذلك، فولّدهما بـ ssh-keygen -a 100 -t ed25519. يُنصح باستخدام عبارة سرية واستخدام ssh-agent، مزيد من المعلومات هنا.

  2. حرّر .ssh/config بحيث يحتوي مدخلًا مثل التالي:

    Host vm
        User username_goes_here
        HostName ip_goes_here
        IdentityFile ~/.ssh/id_ed25519
        LocalForward 9999 localhost:8888
    
  3. استخدم ssh-copy-id vm لنسخ مفتاح ssh إلى الخادم.

  4. ابدأ خادم ويب في جهازك الافتراضي بتنفيذ python -m http.server 8888. ادخل إلى خادم الويب بالانتقال إلى http://localhost:9999 في جهازك.

  5. حرّر إعدادات خادم SSH الخاص بك بتنفيذ sudo vim /etc/ssh/sshd_config وعطّل مصادقة كلمة المرور بتعديل قيمة PasswordAuthentication. عطّل تسجيل دخول الجذر (Root) بتعديل قيمة PermitRootLogin. أعد تشغيل خدمة ssh بـ sudo service sshd restart. حاول الاتصال عبر ssh مرة أخرى.

  6. (تحدٍّ) ثبّت mosh في الجهاز الافتراضي وأنشئ اتصالًا. ثم افصل محوّل الشبكة (Network Adapter) الخاص بالخادم/الجهاز الافتراضي. هل يمكن لـ mosh التعافي من ذلك بشكل صحيح؟

  7. (تحدٍّ) ابحث في ما تفعله العلامتان -N و -f في ssh وتحقق من أمر لتحقيق إعادة توجيه المنافذ في الخلفية (Background Port Forwarding).