Logo

كتابة سكربتات Bash - الجزء الأول

28 دقيقة قراءة
شرائح الدرس
1 / 17

نظم التشغيل 1 - الدرس الحادي عشر

كتابة سكربتات Bash - الجزء الأول

كتابة سكربت Bash ومنحه صلاحية التشغيل وتشغيله، بحيث يأخذ مدخلات ويتخذ قرارات وينفذ عمليات حسابية.

بنهاية هذا الدرس ستكون قادرا على كتابة سكربت Bash يأخذ إدخالا، ويتخذ قرارات، ويقوم بعمليات حسابية.

الأهداف

  • شرح ما هو سكربت Bash ولماذا سطره الأول هو #!/bin/bash.
  • إنشاء ملف سكربت من الطرفية، وجعله قابلا للتنفيذ بـ chmod، وتشغيله بالطريقتين bash script.sh و ./script.sh.
  • تخزين القيم في متغيرات وقراءتها مرة أخرى بـ $.
  • أخذ الإدخال من لوحة المفاتيح بـ echo متبوعا بـ read، ومن سطر الأوامر بالوسائط الموضعية $1 و $2 و $3.
  • قراءة حالة خروج آخر أمر من $? وإرجاع حالة من سكربت بـ exit.
  • إجراء عمليات حسابية على الأعداد الصحيحة بـ $(( )) و (( ))، وعمليات حسابية على الأعداد العشرية بـ bc.
  • اتخاذ قرارات بـ if و elif و else باستخدام أقواس الاختبار [[ ]]، والتعرف على الصيغة الأقدم [ ] عند مواجهتها.

المتطلبات السابقة

  • طرفية Linux.
  • echo و cat وعاملا إعادة التوجيه > و >>، بالإضافة إلى صلاحيات الملفات و chmod (الصيغة الرمزية u+x والصيغة الرقمية 755).
  • محرر نصوص (nano كاف)؛ كل سكربت في هذا الدرس يمكن إنشاؤه أيضا بـ echo وإعادة التوجيه.
  • حاسبة bc، تستخدم لاحقا في هذا الدرس. إذا قال bc --version أن الأمر غير موجود، ثبتها بـ sudo apt install bc.

خلفية

كل سكربت في هذا الدرس يقدم باسم ملف. لإعادة إنتاج الناتج الظاهر، احفظ القائمة تحت ذلك الاسم (بـ nano name.sh، أو بـ echo وإعادة التوجيه كما هو موضح أدناه) وشغل bash name.sh. المهام تعيد استخدام عدد من هذه الملفات.

المفهوم الأول: ما هو سكربت Bash

التعريف. Bash هي لغة الأوامر الافتراضية في Linux. سكربت Bash هو ملف نصي عادي يحمل نفس الأوامر التي تكتبها عند الـ prompt، أمرا في كل سطر، وينفذها برنامج bash من الأعلى إلى الأسفل.

الغرض. أي مهمة تكررها عند الـ prompt يمكن كتابتها مرة واحدة داخل سكربت وتنفيذها بأمر واحد. السكربت يمكنه أيضا أخذ إدخال، واتخاذ قرارات، والإبلاغ عن نتائج، وهذا ما يجعله برنامجا صغيرا.

كيف يعمل. عند تشغيل سكربت، تبدأ عملية bash جديدة، تقرأ الملف سطرا بسطر وتنفذ كل سطر تماما كما لو كنت كتبته أنت. السطر الأول يخبر النظام بالمفسر (interpreter) الذي يجب استخدامه.

المفهوم الثاني: إنشاء وتشغيل سكربت

سطر الـ shebang. السطر الأول من كل سكربت في هذا الدرس هو:

#!/bin/bash

#! (يسمى "الـ shebang") متبوعا بالمسار المطلق للمفسر يخبر الـ kernel: "شغل هذا الملف بـ /bin/bash". الحرفان #! يجب أن يكونا أول حرفين في الملف تماما. ثلاثة أشكال ستقابلها:

  • #! /bin/bash، بمسافة بعد #!، يعمل أيضا؛ المسافة مسموحة.
  • مسار مكتوب خطأ مثل #!/bin/bas يسمي مفسرا غير موجود. ./script.sh يرفض البدء ويطبع رسالة مثل cannot execute: required file not found (الصياغة الدقيقة تعتمد على نسخة Bash).
  • أي حرف قبل #!، مثل .#!/bin/bash، يحول السطر الأول إلى أمر عادي غير موجود. التشغيل يطبع line 1: .#!/bin/bash: No such file or directory والملف لا يعد سكربت Bash صحيحا بعد ذلك.

إنشاء الملف. يمكنك فتح محرر، أو بناء الملف بـ echo وإعادة التوجيه. تذكر العاملين:

  • > ينشئ الملف أو يستبدل محتواه.
  • >> يضيف سطرا في نهاية الملف (وينشئه إذا لم يكن موجودا).
echo '#!/bin/bash' > hello.sh
echo 'echo "Hello from FCIS"' >> hello.sh
cat -n hello.sh

سطرا بسطر:

  1. echo '#!/bin/bash' > hello.sh ينشئ الملف بالـ shebang كسطر أول. علامات الاقتباس المفردة تمنع الـ shell من تفسير !.
  2. echo 'echo "Hello from FCIS"' >> hello.sh يضيف السطر الثاني؛ علامات الاقتباس المفردة الخارجية تنقل علامات الاقتباس المزدوجة الداخلية إلى الملف كما هي.
  3. cat -n hello.sh يعرض الملف بأرقام الأسطر لتتحقق من السطر الأول.

الناتج المتوقع:

     1  #!/bin/bash
     2  echo "Hello from FCIS"

طريقتان لتشغيله. الأمران يختلفان في نقطة مهمة واحدة: الصلاحيات.

الأمرماذا يحدثيحتاج صلاحية تنفيذ؟
bash hello.shتشغل bash بنفسك وتعطيه الملف كوسيط. الـ shebang يتم تجاهله لأنك اخترت المفسر بنفسك.لا، صلاحية القراءة كافية.
./hello.shتطلب من الـ kernel تشغيل الملف كبرنامج. الـ kernel يقرأ الـ shebang ويشغل /bin/bash لك.نعم، الملف يجب أن يحمل x.

ملف جديد أنشئ بـ echo يحمل عادة صلاحيات -rw-rw-r-- (وعلى بعض الأنظمة -rw-r--r--؛ عمود المجموعة يعتمد على إعداد النظام الافتراضي). في كل الحالات لا يوجد x في أي مكان، لذلك ./hello.sh يفشل بـ Permission denied حتى تضيف بت التنفيذ:

chmod u+x hello.sh

chmod u+x يضيف صلاحية التنفيذ للمالك (u)، فتحول -rw-rw-r-- إلى -rwxrw-r--. الصيغة الرقمية chmod 755 hello.sh (المالك rwx، المجموعة r-x، الآخرون r-x) هي الخيار الشائع للسكربتات التي تشارك مع مستخدمين آخرين.

الـ ./ في ./hello.sh تخبر الـ shell أن الملف موجود في المجلد الحالي.

flowchart LR
    A[Write the script file] --> B{"First line is #!/bin/bash?"}
    B -- no --> A
    B -- yes --> C[chmod u+x script.sh]
    C --> D[./script.sh]
    A --> E[bash script.sh]
    E --> F[bash reads the file line by line]
    D --> F
    F --> G[Output on the terminal]

الرسم يوضح المسارين: bash script.sh يعمل بمجرد وجود الملف، بينما ./script.sh يحتاج shebang صحيح وبت التنفيذ.

المفهوم الثالث: التعليقات (Comments)

التعريف. التعليق (comment) هو نص داخل السكربت يتجاهله bash وقت التنفيذ. يوجد فقط للقارئ البشري.

الغرض. التعليقات تشرح ما تفعله كل مجموعة أسطر ولماذا، حتى تتمكن أنت (أو زميلك) من فهم السكربت بعد شهور.

كيف يعمل. # يبدأ تعليقا في سطر واحد؛ كل ما بعده في هذا السطر يتم تجاهله. لعدة أسطر، لا توجد صيغة مخصصة في Bash، لكن الأسلوب : ' ... ' يعمل. : أمر مدمج (built-in) لا يفعل شيئا ويرجع نجاحا. علامات الاقتباس المفردة تحول كل ما بينها وبين علامة ' التالية إلى وسيط واحد، وهذا الوسيط هو ما يتجاهله :. لهذا السبب، نص التعليق نفسه يجب ألا يحتوي على ': فاصلة عليا شاردة (كما في isn't) تنهي التعليق قبل موعده والتشغيل يفشل برسالة unexpected EOF while looking for matching.

احفظ ما يلي باسم comment.sh:

#!/bin/bash
#Take a number as input
echo "Enter a number"
read a
: '
Check the input number is
less than 10 or greater than 10 or equal to 10
'
if [[ $a -lt 10 ]]
then
echo "The number is less than 10"
elif [[ $a -gt 10 ]]
then
echo "The number is greater than 10"
else
echo "The number is equal to 10"
fi

سطرا بسطر:

  1. #!/bin/bash هو الـ shebang. لاحظ أن الـ shebang هو السطر الوحيد الذي يبدأ بـ # والذي ليس تعليقا بالنسبة للـ kernel؛ أما بالنسبة لـ bash نفسه فهو يبقى تعليقا، ولهذا لا يتعارض الدوران أبدا.
  2. #Take a number as input تعليق في سطر واحد.
  3. echo "Enter a number" يطبع سؤالا.
  4. read a ينتظر سطرا من الإدخال من لوحة المفاتيح ويخزنه في المتغير a.
  5. : ' يفتح التعليق متعدد الأسطر: السطران النصيان وعلامة ' الختامية تشكل معا الوسيط الواحد الذي يرميه :.
  6. if [[ $a -lt 10 ]] يختبر إذا كان a أصغر من 10 (-ltthen يبدأ جسم الفرع.
  7. elif [[ $a -gt 10 ]] اختبار ثان، أكبر من (-gt)، يفحص فقط إذا كان الأول خاطئا.
  8. else تمسك الحالة المتبقية (يساوي 10)، و fi تغلق العبارة كاملة.

شغل bash comment.sh ثلاث مرات، مجيبا بـ 3 ثم 10 ثم 90. الناتج المتوقع للتشغيلات الثلاث (إجابتك المكتوبة في السطر بعد كل سؤال):

Enter a number
3
The number is less than 10
Enter a number
10
The number is equal to 10
Enter a number
90
The number is greater than 10

المفهوم الرابع: الطباعة بـ echo

التعريف. echo هو الأمر المستخدم في Bash لطباعة الناتج على الطرفية. يطبع وسائطه متبوعة بسطر جديد.

الغرض. السكربتات تستخدم echo لكل رسالة: أسئلة قبل read، ونتائج بعد عملية حسابية، ورسائل خطأ.

كيف يعمل. كل echo يكتب نصه ثم ينقل المؤشر إلى السطر التالي. الخيار -n يوقف ذلك السطر الجديد الأخير، فما يطبع بعده يستمر في السطر نفسه.

احفظ ما يلي باسم echo_test.sh:

#!/bin/bash
#Print the first text
echo "Print text with a new line"
#Print the second text
echo -n "Print text without a new line"

سطرا بسطر: أول echo يطبع نصه وينتقل لسطر جديد؛ الثاني يطبع نصه ويبقى في هذا السطر، لذلك الـ prompt التالي للـ shell يظهر ملتصقا به. الناتج المتوقع لـ bash echo_test.sh:

Print text with a new line
Print text without a new line$

الـ $ في النهاية هو prompt الـ shell عندك (على Ubuntu يبدو مثل user@host:~$)، وليس جزءا من ناتج السكربت.

المفهوم الخامس: المتغيرات (Variables)

التعريف. المتغير (variable) هو مكان له اسم يخزن قيمة (نص أو رقم).

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

كيف يعمل. التخصيص (assignment) يستخدم name=value بلا مسافات حول = وبلا $. قراءة القيمة تستخدم $name. المتغيرات يمكن إنشاؤها عند الـ prompt أو داخل سكربت. اكتب هذين السطرين عند الـ prompt:

mystr="I like bash programming"
echo $mystr

السطر الأول يخزن النص في mystr؛ السطر الثاني يوسع $mystr إلى قيمته قبل أن يعمل echo. الناتج المتوقع:

I like bash programming

الاقتباس (quoting) مهم عند طباعة متغير:

  • داخل علامات الاقتباس المزدوجة، $name يستبدل بقيمته: echo "$mystr" يطبع النص.
  • داخل علامات الاقتباس المفردة لا يستبدل شيء: echo '$mystr' يطبع حرفيا $mystr.

متغيران مكتوبان أحدهما بجانب الآخر يلتصقان ببساطة (هذه هي طريقة ربط النصوص، concatenation). احفظ ما يلي باسم concat.sh:

#!/bin/bash
string1="I like "
string2="Bash Programming"
echo "$string1$string2"

سطرا بسطر: نصان يخزنان، ثم يطبعان أحدهما بعد الآخر داخل زوج واحد من علامات الاقتباس المزدوجة. الناتج المتوقع لـ bash concat.sh: I like Bash Programming.

المتغير يمكنه أيضا أن يحمل رقما ويقارن بقيمة أخرى. احفظ ما يلي باسم var.sh:

#!/bin/bash
echo "Enter a number"
read a
b=100
if [[ $a -eq $b ]]
then
echo "Numbers are equal"
else
echo "Numbers are not equal"
fi

سطرا بسطر: الرقم المكتوب يذهب إلى a؛ b=100 تخزن رقما بنفس طريقة تخزين نص؛ [[ $a -eq $b ]] صحيحة فقط عندما تكون القيمتان متساويتين كأرقام (-eq، مشروحة في المفهوم العاشر)؛ إحدى الرسالتين تطبع. شغل bash var.sh مرتين، مجيبا بـ 56 ثم 100. الناتج المتوقع للتشغيلتين:

Enter a number
56
Numbers are not equal
Enter a number
100
Numbers are equal

المفهوم السادس: قراءة الإدخال من لوحة المفاتيح

التعريف. read variable يوقف السكربت مؤقتا، ينتظر المستخدم أن يكتب سطرا ويضغط Enter، ويخزن هذا السطر في المتغير.

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

كيف يعمل. read لا يطبع شيئا بنفسه، لذلك يقترن دائما بـ echo في السطر الذي يسبقه: الـ echo يعرض السؤال، والـ read يجمع الإجابة. احفظ ما يلي باسم ask.sh:

#!/bin/bash
echo "Enter your name"
read name
echo "Enter your age"
read age
echo "$name is $age years old"

سطرا بسطر: السطر الثاني يطبع السؤال الأول في سطره الخاص؛ السطر الثالث يخزن الاسم المكتوب؛ السطران الرابع والخامس يفعلان الشيء نفسه للعمر؛ السطر السادس يطبع القيمتين معا داخل نص واحد. جلسة الطرفية المتوقعة لـ bash ask.sh (كل إجابة تقع في السطر بعد سؤالها):

Enter your name
Student1
Enter your age
20
Student1 is 20 years old

المفهوم السابع: الوسائط الموضعية (Positional Parameters)

التعريف. الوسائط الموضعية (positional parameters) هي الكلمات المكتوبة بعد اسم السكربت على سطر الأوامر. داخل السكربت تتوفر كـ $1 (الأولى)، $2 (الثانية)، $3 (الثالثة).

الغرض. تسمح للمستخدم بتمرير قيم بدون أن يسأل تفاعليا، وهذا ما يجعل السكربت قابلا للاستخدام من سكربتات أخرى.

كيف يعمل. الـ shell يقسم سطر الأوامر عند الفراغات ويخصص كل كلمة للرقم التالي قبل أن يبدأ السكربت. احفظ ما يلي باسم subtract.sh:

#!/bin/bash
a=$1
b=$2
((result=$a-$b))
echo "The subtraction of $a-$b=$result"

سطرا بسطر: $1 و $2 تنسخان إلى a و b؛ ((result=$a-$b)) تحسب الطرح (انظر المفهوم التاسع)؛ الرسالة تطبع القيم الثلاث. تشغيل bash subtract.sh 50 20 يطبع:

The subtraction of 50-20=30

الوسائط يمكن استخدامها مباشرة أيضا، كما في ((result=$1*$2))، بدون نسخها أولا.

المفهوم الثامن: حالة الخروج و $?

التعريف. كل أمر ينتهي بحالة خروج (exit status): عدد صحيح حيث 0 يعني نجاحا وأي قيمة أخرى تعني فشلا أو حالة معينة. المتغير الخاص $? يحمل حالة خروج آخر أمر تم تنفيذه.

الغرض. السكربت يمكن أن ينتهي بـ exit N ليبلغ نتيجة لمن شغله، والمستدعي يمكنه قراءة هذا الرقم من $? ويقرر ما يفعله بعد ذلك.

كيف يعمل. كل أمر يستبدل $?، لذلك اقرأها فورا بعد الأمر الذي يهمك، أو انسخها إلى متغير. احفظ ما يلي باسم check.sh؛ لا يطبع شيئا عن قراره ويبلغ عنه فقط عبر exit:

#!/bin/bash
echo "Enter a numeric value"
read n
# Return 0 when the value is less than or equal to 100, 1 otherwise
if [[ $n -le 100 ]]
then
exit 0
else
exit 1
fi

سطرا بسطر: بعد قراءة n، -le تختبر "أصغر من أو يساوي"؛ exit 0 تنهي السكربت بحالة 0 (نجاح)؛ exit 1 تنهيه بحالة 1، والتي يستخدمها هذا السكربت لتعني "n كانت أكبر من 100". أي رقم غير صفري مسموح ومعناه يقرره كاتب السكربت. لا يعمل أي شيء بعد exit.

اقرأ الحالة عند الـ prompt، مرتين، مجيبا بـ 55 ثم 110:

bash check.sh
echo $?
bash check.sh
echo $?

الناتج المتوقع للأوامر الأربعة:

Enter a numeric value
55
0
Enter a numeric value
110
1

الـ echo $? يجب أن يأتي مباشرة بعد bash check.sh؛ أي أمر يوضع بينهما يستبدل الـ 0 أو 1 بحالته الخاصة. سكربت ثان يمكنه استدعاء check.sh بالطريقة نفسها ويحول $? إلى رسالة. عملية التسليم تبدو كالتالي:

sequenceDiagram
    participant C as caller.sh
    participant K as check.sh
    participant U as Keyboard
    C->>K: bash check.sh
    K->>U: Enter a numeric value
    U-->>K: n
    alt n <= 100
        K-->>C: exit 0
    else n > 100
        K-->>C: exit 1
    end
    Note over C: $? now holds 0 or 1
    C->>C: if [ $? -eq 1 ] print "greater than 100" else print "less than or equal to 100"

القرار ينتقل فقط عبر حالة الخروج: check.sh لا يطبع النتيجة أبدا، و caller.sh لا يرى الرقم المكتوب أبدا. واحدة من المهام أدناه تبني caller.sh ثم تكسرها عمدا.

المفهوم التاسع: العمليات الحسابية (Arithmetic)

التعريف. Bash لا يحسب 2+3 من نفسه: بالنسبة للـ shell هذا مجرد نص. العمليات الحسابية يجب طلبها صريحا.

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

كيف يعمل. الطريقة البسيطة هي الأقواس المزدوجة، بصيغتين: $(( expression )) تستبدل بالقيمة، فيمكن وضعها داخل نص أو على يمين =؛ (( expression )) تحسب وتخصص في مكانها، كما في ((result=$a-$b)) أو (( N += 10 )). داخل $(( )) و (( )) يمكن كتابة المتغير مع $ أو بدونه: $((age + 1)) و $(($age + 1)) تعطيان النتيجة نفسها. الأقواس المزدوجة تعمل مع الأعداد الصحيحة فقط: $((7 / 2)) تعطي 3، لأن الجزء الكسري يسقط. Bash لديها أيضا أمران let و expr وحاسبة bc الخارجية؛ هذا الدرس يستخدم bc، وهي التي تفهم الأعداد العشرية.

احفظ ما يلي باسم arith.sh:

#!/bin/bash
# Calculate the sum
result=$((50+25))
# Print summation value
echo "sum = $result"
 
# Calculate the division
result=$((50/25))
# Print division value
echo "division = $result"
 
# Assign a value to N
N=10
# Doing pre-increment
((--N))
# Print the value of N
echo "Value after decrement = $N"
 
# Using shorthand operator
(( N += 10 ))
# Print the value of N
echo "Value after adding 10 = $N"

سطرا بسطر: $((50+25)) تحسب 75 وتخزنها؛ $((50/25)) تحسب 2؛ ((--N)) تطرح 1 من N، فتصبح N تساوي 9 (((N--)) بمفردها في سطر تفعل الشيء نفسه)؛ (( N += 10 )) تضيف 10 في مكانها، فتصبح 19. الناتج المتوقع لـ bash arith.sh:

sum = 75
division = 2
Value after decrement = 9
Value after adding 10 = 19

عندما تهم الأعداد العشرية، سلم التعبير إلى bc عبر أنبوب (pipe). حساب مساحة دائرة بنصف قطر 5 يكون:

area=$(echo $radius*$radius*3.14 | bc)

echo تطبع التعبير 5*5*3.14، الأنبوب | يمرر هذا النص إلى bc، و bc تطبع 78.50. $( ... ) هو استبدال أوامر (command substitution): يشغل الأمر بداخله ويستبدل بناتج هذا الأمر، وهذه هي الطريقة التي تصل بها الإجابة المطبوعة إلى المتغير area. واحدة من المهام أدناه تضع الصيغتين جنبا إلى جنب.

المفهوم العاشر: القرارات بـ if و elif و else

التعريف. عبارة if تشغل مجموعة أوامر فقط عندما يكون الشرط صحيحا. elif تضيف شروطا أخرى تجرب بالترتيب، و else تعمل عندما لا يكون أي منها صحيحا.

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

البنية.

if CONDITION
then
    commands
elif OTHER_CONDITION
then
    commands
else
    commands
fi

كل سطر if أو elif يتبعه then (في السطر التالي، أو في السطر نفسه بعد ;)، والعبارة تنتهي بـ fi.

أقواس الاختبار. الشرط يكتب بين أقواس، ويوجد نوعان منها. [[ ... ]] هي الصيغة الخاصة بـ Bash: تقبل && و || مباشرة بين اختبارين ولا تتعطل عندما يكون المتغير فارغا. [ ... ] هي الصيغة الأقدم التي تعمل في كل shell، لكنها تحتاج متغيراتها موضوعة بين علامات اقتباس ("$text") ولا يمكنها أخذ && داخل الأقواس. القاعدة في هذا الدرس: اكتب [[ ]]؛ [ ] تظهر في قائمتين فقط لتتعرف عليها. كلاهما يحتاج مسافة بعد القوس الفاتح وقبل القوس المغلق. بداخلهما:

العاملالمعنىمثال
-eqتساوي الأرقام[[ $a -eq $b ]]
-lt, -leأصغر من / أصغر من أو يساوي[[ $n -le 100 ]]
-gt, -geأكبر من / أكبر من أو يساوي[[ $age -ge 18 ]]
==تساوي النصوص[[ $operator == '+' ]]
&&, ||كلا الشرطين / أحد الشرطين (داخل [[ ]])[[ $age -ge 18 && $code -eq 1100 ]]

العوامل الرقمية تقارن القيم كأرقام؛ == تقارن النص حرفا بحرف. احفظ ما يلي باسم movie.sh:

#!/bin/bash
 
echo "Enter your code"
read code
echo "Enter your age"
read age
 
if [[ $age -ge 18 && $code -eq '1100' ]]
then
echo "You are eligible to see the movie"
else
echo "You are not eligible to see the movie"
fi

سطرا بسطر: قيمتان تقرآن؛ الشرط صحيح فقط عندما يكون age على الأقل 18 و code يساوي 1100؛ إحدى الرسالتين تطبع. علامات الاقتباس المفردة حول '1100' غير ضارة هنا لأن -eq تحول الطرفين إلى أرقام؛ غير مطلوبة، ومهام هذا الدرس تكتب الأرقام بلا علامات اقتباس. شغل bash movie.sh مرتين، بكود 1100 وعمر 5، ثم كود 1100 وعمر 45. الناتج المتوقع للتشغيلتين:

Enter your code
1100
Enter your age
5
You are not eligible to see the movie
Enter your code
1100
Enter your age
45
You are eligible to see the movie

مقارنة النصوص تستخدم الشكل نفسه بـ ==. احفظ ما يلي باسم compare.sh؛ مكتوب بالأقواس الأقدم [ ]:

#!/bin/bash
echo "Enter any string value"
read text
#Check the input data is equivalent to "Python"
if [ $text == "Python" ]; then
echo "You like Python."
else
echo "You like PERL"
fi

سطرا بسطر: النص المكتوب يقارن بالكلمة Python؛ لاحظ then في السطر نفسه بعد ;. إدخال PERL يطبع You like PERL؛ إدخال Python يطبع You like Python..

ملاحظة إضافية، ليست جزءا من القائمة أعلاه: شغل bash compare.sh مرة أخرى واضغط Enter بدون كتابة أي شيء. السطر يصبح [ == "Python" ] و Bash يشتكي بـ [: ==: unary operator expected قبل أن يطبع You like PERL. كتابة "$text" داخل [ ]، أو استخدام [[ $text == "Python" ]]، يجعل الإدخال الفارغ يمر بهدوء. لهذا السبب كل سكربت مهمة في هذا الدرس يستخدم [[ ]].

flowchart TD
    S[read mark] --> C1{"mark >= 85?"}
    C1 -- yes --> A[print Grade: A]
    C1 -- no --> C2{"mark >= 75?"}
    C2 -- yes --> B[print Grade: B]
    C2 -- no --> C3{"mark >= 65?"}
    C3 -- yes --> C[print Grade: C]
    C3 -- no --> C4{"mark >= 50?"}
    C4 -- yes --> D[print Grade: D]
    C4 -- no --> F[print Grade: F]
    A --> E[fi]
    B --> E
    C --> E
    D --> E
    F --> E

الرسم هو سلسلة if/elif/else المستخدمة في مهمة تصنيف الدرجات أدناه: الشروط تختبر من الأعلى إلى الأسفل، أول شرط صحيح يفوز، وفرع واحد فقط يعمل.

مهام المعمل

اعمل داخل مجلد جديد وخال حتى لا تختلط ملفات السكربتات المنشأة أدناه بأي شيء آخر.

المهمة الأولى: إنشاء وتصريح وتشغيل أول سكربت لك

  1. بناء الملف بـ echo وإعادة التوجيه:

    echo '#!/bin/bash' > hello.sh
    echo '# My first Bash script' >> hello.sh
    echo 'echo "Hello from FCIS"' >> hello.sh
    echo 'echo -n "No newline here. "' >> hello.sh
    echo 'echo "Same line."' >> hello.sh

    سطرا بسطر: السطر الأول ينشئ الملف بالـ shebang (> تستبدل، كما في المفهوم الثاني)؛ السطر الثاني يضيف تعليقا في سطر واحد (المفهوم الثالث)؛ السطر الثالث يضيف echo يطبع نصه وينتقل لسطر جديد؛ السطر الرابع يضيف echo -n يطبع نصه ويبقى في السطر نفسه (المفهوم الرابع)؛ السطر الخامس يضيف echo ثانيا، ولذلك يقع نصه في السطر نفسه الذي يسبقه. كل echo عند الـ prompt يوضع بين علامات اقتباس مفردة حتى تصل ! و # وعلامات الاقتباس المزدوجة الداخلية إلى الملف كما هي.

  2. تحقق من المحتوى والصلاحيات:

    cat -n hello.sh
    ls -l hello.sh

    الناتج المتوقع (المالك والمجموعة والحجم والتاريخ ستكون خاصة بك):

         1  #!/bin/bash
         2  # My first Bash script
         3  echo "Hello from FCIS"
         4  echo -n "No newline here. "
         5  echo "Same line."
    -rw-rw-r-- 1 student student 104 Nov 20 10:00 hello.sh

    عمود المجموعة قد يقرأ r-- عندك بدلا من rw-؛ والمهم أنه لا يظهر أي x في أي مكان.

  3. شغله بتسمية المفسر صريحا:

    bash hello.sh

    الناتج المتوقع:

    Hello from FCIS
    No newline here. Same line.
  4. حاول تشغيله كبرنامج قبل إضافة صلاحية التنفيذ:

    ./hello.sh

    الناتج المتوقع:

    bash: ./hello.sh: Permission denied
  5. أضف بت التنفيذ وشغله مرة أخرى:

    chmod u+x hello.sh
    ls -l hello.sh
    ./hello.sh

    الناتج المتوقع:

    -rwxrw-r-- 1 student student 104 Nov 20 10:00 hello.sh
    Hello from FCIS
    No newline here. Same line.

    الفرق الوحيد بين الخطوتين 4 و 5 هو x في صلاحيات المالك.

المهمة الثانية: المتغيرات وإدخال لوحة المفاتيح

  1. أنشئ profile.sh بالمحتوى التالي:

    #!/bin/bash
    # Ask for a name and an age, then build a message
    echo "Enter your name"
    read name
    echo "Enter your age"
    read age
    greeting="Welcome to the OS lab"
    echo "$greeting, $name"
    echo "Next year you will be $((age + 1))"

    سطرا بسطر: السطر الثالث يطبع السؤال الأول؛ السطر الرابع يخزن الاسم المكتوب؛ السطران الخامس والسادس يفعلان الشيء نفسه للعمر؛ السطر السابع يخزن تحية ثابتة؛ السطر الثامن يطبع التحية والاسم موصولين بفاصلة؛ السطر التاسع يضيف واحدا للعمر بـ $(( )) داخل النص (age مكتوبة بدون $ داخل الأقواس، وهذا يسمح به المفهوم التاسع).

  2. شغله وأجب على السؤالين بـ Student1 و 20:

    bash profile.sh

    جلسة الطرفية المتوقعة (كل إجابة تقع في السطر بعد سؤالها):

    Enter your name
    Student1
    Enter your age
    20
    Welcome to the OS lab, Student1
    Next year you will be 21
  3. غير السطر الثامن إلى علامات اقتباس مفردة: echo '$greeting, $name'. شغله مرة أخرى بالإجابتين نفسيهما. هذا السطر يطبع الآن النص الحرفي $greeting, $name: علامات الاقتباس المفردة توقف توسيع المتغيرات. أعده كما كان قبل الاستمرار.

المهمة الثالثة: آلة حاسبة تعمل بالوسائط الموضعية

  1. أنشئ calc.sh:

    #!/bin/bash
    # Usage: ./calc.sh number operator number
    echo "Argument values are: $1 $2 $3"
    operand1=$1
    operator=$2
    operand2=$3
    if [[ $operator == '+' ]]
    then
        ((result=$operand1+$operand2))
    elif [[ $operator == '-' ]]
    then
        ((result=$operand1-$operand2))
    elif [[ $operator == 'x' ]]
    then
        ((result=$operand1*$operand2))
    elif [[ $operator == '/' ]]
    then
        ((result=$operand1/$operand2))
    else
        echo "Unknown operator: $operator"
        exit 1
    fi
    echo "Result is = $result"

    سطرا بسطر: السطر الثالث يطبع الوسائط الثلاثة كما وردت؛ الأسطر من الرابع إلى السادس تنسخها إلى متغيرات مسماة (العامل هو الكلمة الثانية، لذلك $2 تذهب إلى operator)؛ سلسلة if/elif تقارن نص العامل بـ == وتشغل التخصيص المطابق بـ (( ))؛ فرع else يبلغ عن عامل غير معروف ويخرج بحالة 1؛ السطر الأخير يطبع النتيجة. الضرب يطلب بالحرف x، لذلك العوامل الأربعة المقبولة هي + و - و x و /.

  2. اجعله قابلا للتنفيذ وشغله أربع مرات:

    chmod 755 calc.sh
    ./calc.sh 6 + 3
    ./calc.sh 6 - 3
    ./calc.sh 6 x 3
    ./calc.sh 6 / 3

    الناتج المتوقع:

    Argument values are: 6 + 3
    Result is = 9
    Argument values are: 6 - 3
    Result is = 3
    Argument values are: 6 x 3
    Result is = 18
    Argument values are: 6 / 3
    Result is = 2
  3. شغله بعامل لا يعرفه، ثم اقرأ حالة الخروج:

    ./calc.sh 6 % 3
    echo $?

    الناتج المتوقع:

    Argument values are: 6 % 3
    Unknown operator: %
    1
  4. شغل ./calc.sh 7 / 2. سطر النتيجة المتوقع: Result is = 3. اكتب في جملة واحدة لماذا الإجابة ليست 3.5 (السبب موجود في المفهوم التاسع).

  5. إدخالان لا يحرس السكربت ضدهما. شغل ./calc.sh 6 / 0 ثم ./calc.sh 6 - (الوسيط الثالث مفقود). الناتج المتوقع:

    Argument values are: 6 / 0
    ./calc.sh: line 18: ((: result=6/0: division by 0 (error token is "0")
    Result is = 
    Argument values are: 6 - 
    ./calc.sh: line 12: ((: result=6-: syntax error: operand expected (error token is "-")
    Result is = 

    الوسيط المفقود يكون فارغا ببساطة، لذلك $operand2 يختفي من التعبير؛ في التشغيلتين سطر (( )) يفشل، result لا تخصص أبدا، و echo الأخير يطبع قيمة فارغة. إضافة فحوصات لهاتين الحالتين متروكة للدرس التالي.

المهمة الرابعة: الأعداد الصحيحة بالأقواس المزدوجة، الأعداد العشرية بـ bc

  1. أنشئ area.sh:

    #!/bin/bash
    # Circle area with double parentheses and with bc
    echo "Enter the radius"
    read radius
     
    # 1) double parentheses: whole numbers only
    square=$((radius * radius))
    echo "radius squared = $square"
    rough=$((radius * radius * 3))
    echo "rough area (pi taken as 3) = $rough"
     
    # 2) bc: decimals allowed
    area=$(echo $radius*$radius*3.14 | bc)
    echo "Area of the circle is $area"
     
    # integer division drops the fraction
    echo "7 / 2 with double parentheses = $((7 / 2))"
     
    # decrement and shorthand operators
    N=10
    ((--N))
    echo "Value after decrement = $N"
    (( N += 10 ))
    echo "Value after adding 10 = $N"

    سطرا بسطر: نصف القطر يطلب بـ echo ويخزن بـ read؛ $(( )) تربعه ثم تضربه في 3، بديل من الأعداد الصحيحة عن باي؛ bc تستقبل 5*5*3.14 عبر الأنبوب وترجع الإجابة العشرية، التي يخزنها استبدال الأوامر في area؛ سطر 7 / 2 يظهر الكسر الساقط؛ المجموعة الأخيرة تكرر النقص و اختصار += من المفهوم التاسع.

  2. شغله بنصف قطر 5:

    bash area.sh

    الناتج المتوقع:

    Enter the radius
    5
    radius squared = 25
    rough area (pi taken as 3) = 75
    Area of the circle is 78.50
    7 / 2 with double parentheses = 3
    Value after decrement = 9
    Value after adding 10 = 19
  3. غير سطر rough= لاستخدام القيمة الحقيقية لباي داخل الأقواس المزدوجة: rough=$((radius * radius * 3.14)). شغله مرة أخرى بنصف قطر 5. الناتج المتوقع لهذا السطر:

    area.sh: line 9: radius * radius * 3.14: syntax error: invalid arithmetic operator (error token is ".14")
    rough area (pi taken as 3) = 

    الأقواس المزدوجة تتوقف عند العلامة العشرية؛ هذا هو سبب مرور سطر المساحة عبر bc. أعد 3.

المهمة الخامسة: إرجاع رمز خروج من سكربت واستقباله في سكربت آخر

  1. أنشئ check.sh تماما كما ورد في المفهوم الثامن:

    #!/bin/bash
    echo "Enter a numeric value"
    read n
    # Return 0 when the value is less than or equal to 100, 1 otherwise
    if [[ $n -le 100 ]]
    then
        exit 0
    else
        exit 1
    fi

    سطرا بسطر: رقم يقرأ؛ -le تختبره مقابل 100؛ السكربت ينتهي فورا بحالة 0 أو 1، ولا يطبع شيئا عن القرار. القرار ينتقل فقط عبر حالة الخروج.

  2. أنشئ caller.sh، المشارك الأيسر في رسم التسلسل من المفهوم الثامن:

    #!/bin/bash
    # Run check.sh, then read its exit code from $?
    bash check.sh
    code=$?
    echo "check.sh returned $code"
    if [ $code -eq 1 ]
    then
        echo "The input number is greater than 100"
    else
        echo "The input number is less than or equal to 100"
    fi

    سطرا بسطر: bash check.sh يشغل السكربت الأول (الذي يسأل عن الرقم)؛ code=$? يحفظ حالة الخروج قبل أن يستبدلها echo التالي؛ الـ if يستخدم الأقواس الأقدم [ ] مع -eq لتحويل الكود إلى رسالة (هذا هو الاستخدام الثاني والأخير لـ [ ] في هذا الدرس؛ code لا تكون فارغة أبدا هنا، فلا حاجة لعلامات اقتباس).

  3. شغل الـ caller مرتين، مجيبا بـ 55 ثم 110:

    bash caller.sh
    bash caller.sh

    الناتج المتوقع للتشغيلتين:

    Enter a numeric value
    55
    check.sh returned 0
    The input number is less than or equal to 100
    Enter a numeric value
    110
    check.sh returned 1
    The input number is greater than 100
  4. اقرأ $? مباشرة بدلا من حفظها. احذف السطرين code=$? و echo "check.sh returned $code" معا، وغير الاختبار إلى if [ $? -eq 1 ]، حتى لا يقف شيء بين bash check.sh و الـ if. شغله بـ 110. الناتج المتوقع:

    Enter a numeric value
    110
    The input number is greater than 100

    لا يزال يعمل لأن $? تقرأ مباشرة بعد bash check.sh. الآن أدرج سطرا واحدا، echo "checking..."، بين bash check.sh و الـ if، وشغله بـ 110 مرة أخرى. الناتج المتوقع:

    Enter a numeric value
    110
    checking...
    The input number is less than or equal to 100

    الرسالة خاطئة لأن $? تحمل الآن حالة الـ echo، وهي 0. اشرح هذا في جملة واحدة، ثم أعد نسخة الخطوة 2 من الملف. (إذا حذفت فقط code=$? وأبقيت على سطر echo "check.sh returned $code"، فإن هذا الـ echo هو الأمر الذي يستبدل $?: التشغيل يطبع check.sh returned بقيمة فارغة متبوعة بالرسالة الخاطئة.)

المهمة السادسة: تصنيف الدرجات بـ if و elif و else

  1. أنشئ grade.sh:

    #!/bin/bash
    # Classify a mark and check exam eligibility
    echo "Enter your mark (0-100)"
    read mark
    echo "Enter your attendance percentage"
    read attendance
     
    if [[ $mark -ge 85 ]]
    then
        echo "Grade: A"
    elif [[ $mark -ge 75 ]]
    then
        echo "Grade: B"
    elif [[ $mark -ge 65 ]]
    then
        echo "Grade: C"
    elif [[ $mark -ge 50 ]]
    then
        echo "Grade: D"
    else
        echo "Grade: F"
    fi
     
    if [[ $mark -ge 50 && $attendance -ge 75 ]]
    then
        echo "You passed the course"
    else
        echo "You did not pass the course"
    fi

    سطرا بسطر: قيمتان تطلبان بـ echo وتخزنان بـ read؛ سلسلة الـ if الأولى هي الرسم من المفهوم العاشر، تختبر من أعلى عتبة إلى أسفلها بحيث تتوقف درجة 90 عند الفرع الأول ولا تصل أبدا إلى -ge 75؛ سلسلة الـ if الثانية تجمع اختبارين رقميين بـ && داخل [[ ]]، فيجب أن يتحقق كلاهما لتطبع رسالة النجاح.

  2. اجعله قابلا للتنفيذ وشغله أربع مرات. كل زوج أدناه هو (درجة، حضور): الرقم الأول يجيب على سؤال الدرجة، والثاني يجيب على سؤال الحضور. الأزواج هي (90، 80) و (70، 90) و (40، 100) و (60، 50):

    chmod u+x grade.sh
    ./grade.sh
    ./grade.sh
    ./grade.sh
    ./grade.sh

    الناتج المتوقع، مجموعة لكل تشغيل:

    Enter your mark (0-100)
    90
    Enter your attendance percentage
    80
    Grade: A
    You passed the course
    Enter your mark (0-100)
    70
    Enter your attendance percentage
    90
    Grade: C
    You passed the course
    Enter your mark (0-100)
    40
    Enter your attendance percentage
    100
    Grade: F
    You did not pass the course
    Enter your mark (0-100)
    60
    Enter your attendance percentage
    50
    Grade: D
    You did not pass the course
  3. انقل فرع -ge 50 كاملا (سطر elif الخاص به، و then الخاص به، و echo "Grade: D") إلى أعلى السلسلة كـ if، واجعل الفرع القديم -ge 85 بمثابة elif. شغله بدرجة 90 وحضور 80. أول سطر متوقع بعد السؤالين: Grade: D. اشرح في جملة واحدة لماذا يهم ترتيب الفروع عندما تتداخل الشروط. أعد الترتيب الأصلي.

  4. عدل السكربت حتى يقبل الدرجة أيضا كوسيط موضعي: استبدل أول سطرين من الجسم (echo "Enter your mark (0-100)" و read mark) بالسطر الواحد mark=$1، وشغل ./grade.sh 88، مجيبا بـ 80 للحضور. الناتج المتوقع:

    Enter your attendance percentage
    80
    Grade: A
    You passed the course

الخلاصة

  • سكربت Bash هو ملف نصي من الأوامر ينفذها bash من الأعلى إلى الأسفل؛ سطره الأول، #!/bin/bash، يسمي المفسر. #! /bin/bash يعمل أيضا، مسار مكتوب خطأ أو حرف قبل #! لا يعمل.
  • bash script.sh يحتاج فقط صلاحية القراءة؛ ./script.sh يحتاج بت التنفيذ (chmod u+x أو chmod 755) و shebang صحيح، و ./ تخبر الـ shell أن ينظر في المجلد الحالي.
  • # يبدأ تعليقا في سطر واحد؛ : ' ... ' هو أسلوب التعليق متعدد الأسطر، ونصه يجب ألا يحتوي على '.
  • خصص بـ name=value (بلا مسافات، بلا $)؛ اقرأ بـ $name؛ علامات الاقتباس المزدوجة توسع المتغيرات، المفردة لا توسعها.
  • إدخال لوحة المفاتيح هو echo للسؤال متبوع بـ read var للإجابة؛ $1 و $2 و $3 تستقبل الكلمات المكتوبة بعد اسم السكربت.
  • كل أمر يترك حالة خروج في $? (0 = نجاح)؛ السكربت يضبط حالته الخاصة بـ exit N، ويجب قراءة $? فورا لأن الأمر التالي يستبدلها.
  • $(( )) و (( )) تقومان بعمليات حسابية على الأعداد الصحيحة (7 / 2 تعطي 3، و 3.14 خطأ صياغي داخلهما)؛ echo expression | bc تتعامل مع الأعداد العشرية؛ let و expr موجودان لكن لا يستخدمان في هذا الدرس.
  • if / elif / else / fi تختار فرعا واحدا بالضبط؛ الشروط تعيش داخل [[ ]] مع مسافات داخل الأقواس؛ استخدم -eq -lt -le -gt -ge للأرقام، و == للنصوص، و && / || داخل [[ ]] لدمج الاختبارات. الأقواس الأقدم [ ] تحتاج متغيرات موضوعة بين علامات اقتباس ولا تستطيع دمج اختبارات داخل الأقواس.
  • الدرس التالي يغطي بقية المادة: حلقات for و while و until، الدوال (functions)، استدعاء سكربت من آخر بـ source و bash و eval و exec، قوائم بـ select و case و $@، معالجة النصوص، قراءة وكتابة الملفات، والأنابيب (pipes).