برمجة Bash، الجزء 1: المتغيرات والإدخال والعمليات الحسابية وجمل if
في الدرسين العمليين الماضيين كتبت أوامر لينكس أمرا بعد أمر. وفي هذا الأسبوع تبدأ تدوينها في ملف: فسكربت Bash (Bash script) ملف من الأوامر ينفذها Bash لك من الأعلى إلى الأسفل. ستنشئ أول سكربتاتك وتشغلها، وتخزن القيم في متغيرات (variables)، وتدع المستخدم يقدم البيانات إدخالا (input)، وتحسب بالأعداد الصحيحة، وتتخذ القرارات بالجملة if. أما الحلقات (loops) والدوال (functions) فتأتي في الأسبوع القادم، في الجزء 2.
كل سكربت في هذه الصفحة محفوظ في مجلد اسمه lab11، وقد جرى تشغيله فعلا على نظام أوبونتو 24.04 (Bash 5.2.21) بحساب مستخدم عادي اسمه student، على جهاز اسمه ubuntu-lab، بإعدادات اللغة الإنجليزية الافتراضية (en_US.UTF-8). وكل خرج (output) معروض هنا هو الخرج الحقيقي لذلك التشغيل. وعندما تعيد تشغيلها ستختلف عندك التواريخ والأوقات وكل ما يعد الملفات في نظامك؛ أما كل ما عدا ذلك فينبغي أن يطابق ما هنا.
الأهداف
بنهاية هذا الدرس ينبغي أن تكون قادرا على:
- شرح معنى سكربت Bash، وكيف يعمل بصفته عملية (process).
- إنشاء سكربت، وإعطائه إذن التنفيذ (execute permission) بالأمر
chmod 755، وتشغيله بالأمر./script.shوبالأمرbash script.sh. - شرح سطر الـshebang
#!/bin/bash، وسبب حاجة السكربت الموجود في المجلد الحالي إلى./قبل اسمه. - تخزين القيم في المتغيرات وقراءتها، واستخدام علامات التنصيص (quotes) المفردة والمزدوجة، وحفظ خرج أمر بالصيغة
$( )، واستخدام وسائط سطر الأوامر (command line arguments)$1و$2و$#و$@وبقية المتغيرات الخاصة (special variables). - تمرير متغير إلى سكربت آخر بالأمر
export. - أخذ الإدخال بالأوامر
readوread -pوread -s، وقراءة عدة كلمات دفعة واحدة، وقراءة البيانات الممررة إلى السكربت عبر أنبوب (pipe)، أي من STDIN. - إجراء العمليات الحسابية (arithmetic) على الأعداد الصحيحة بالأوامر
letوexprو$(( ))و(( ))، وإيجاد طول متغير. - اتخاذ القرارات بالجمل
ifوelseوelifوجملifالمتداخلة (nested)، ومعاملات (operators) الأمرtest، و&&و||وcase، وقراءة حالة الخروج (exit status) بالمتغير$?.
قبل أن تبدأ: مجلد الدرس العملي
افتح طرفية (terminal) وأنشئ مجلدا لهذا الدرس العملي:
$ mkdir ~/lab11
$ cd ~/lab11أنشئ كل سكربت في هذه الصفحة داخل ذلك المجلد باستخدام محرر النصوص (Text Editor)، أو بالأمر nano name.sh في الطرفية، واستخدم اسم الملف المذكور فوق الشيفرة كما هو تماما. ويستخدم عدد من الأمثلة ملف بيانات واحدا. احفظه باسم salesdata.txt:
stall1 apples 20 September 4
stall2 oranges 5 September 7
stall3 watermelons 12 September 10
stall4 peaches 7 September 15الجزء 1: ما سكربت Bash؟
ما السكربتات بالضبط؟
فكر في نص مسرحية أو فيلم: إنه يخبر الممثلين بما يقولونه وما يفعلونه. وسكربت الحاسوب يخبر الحاسوب بما يفعله. وسكربت Bash هو ملف نصي عادي يحتوي على سلسلة من الأوامر، ينفذها Bash واحدا بعد الآخر.
وأهم نقطة ينبغي تذكرها:
كل ما يمكنك تشغيله عادة في سطر الأوامر يمكن وضعه في سكربت، وسيفعل الشيء نفسه تماما. وبالمثل، كل ما يمكنك وضعه في سكربت يمكن تشغيله عادة في سطر الأوامر، وسيفعل الشيء نفسه تماما.
ولا تحتاج إلى تغيير أي شيء: اكتب الأوامر كما تكتبها عند المحث (prompt). فإذا كنت تعرف كيف تنجز شيئا في سطر الأوامر، فأنت تعرف قدرا لا بأس به من برمجة Bash. ومن المتعارف عليه أن تعطى سكربتات Bash الامتداد .sh (مثل myscript.sh)، لكن لينكس نظام لا يعتمد على الامتدادات، فلا يحتاج السكربت إليه ليعمل.
كيف تعمل؟ البرامج والعمليات
البرنامج (program) مجموعة من التعليمات للمعالج (CPU)، مخزنة على القرص. وعندما "نشغل برنامجا" ينسخ لينكس هذه التعليمات إلى الذاكرة (RAM) ويعطي النسخة مساحة لمتغيراتها: وهذه النسخة العاملة هي العملية. فالعملية نسخة قيد التشغيل من برنامج. فإذا شغلت طرفيتان الأمر cp في اللحظة نفسها فهناك عمليتا cp؛ وعندما تنتهيان يدمرهما النظام.
والطرفية نفسها عملية Bash: فهي التي تعطيك المحث. وعندما تبدأ سكربتا فإنه لا يعمل داخل تلك العملية، بل يبدأ عملية جديدة ويعمل داخلها. وسترى أهمية ذلك عندما نصدر المتغيرات في الجزء 2.
سكربتك الأول
احفظ ما يلي باسم myscript.sh:
#!/bin/bash
# A sample Bash script
echo Hello World!سطرا سطرا:
#!/bin/bashهو سطر الـshebang (يأتي شرحه أدناه): وهو يحدد البرنامج الذي يشغل هذا الملف.# A sample Bash scriptتعليق (comment). وكل ما يأتي بعد#في السطر لا ينفذ؛ فهو موجود للقارئ البشري.echo Hello World!يطبع رسالة. ويمكنك كتابة هذا الأمر عند المحث وسيسلك السلوك نفسه تماما.
التشغيل: إذن التنفيذ
قبل أن نتمكن من تشغيل سكربت (أي تنفيذه)، يجب أن يكون له إذن التنفيذ. ولدواعي الأمان لا يمنح هذا الإذن للملف الجديد. ونسيانه لا يسبب ضررا: كل ما يحدث أنك تحصل على رسالة خطأ.
مثال محلول 1: شغل السكربت الجديد
السؤال. انتهيت للتو من حفظ myscript.sh. توقع ما يطبعه كل أمر من هذه الأوامر: ./myscript.sh، وls -l myscript.sh، وchmod 755 myscript.sh، وls -l myscript.sh، و./myscript.sh.
الإجابة.
$ ./myscript.sh
bash: ./myscript.sh: Permission denied
$ ls -l myscript.sh
-rw-r--r-- 1 student student 53 Sep 25 12:10 myscript.sh
$ chmod 755 myscript.sh
$ ls -l myscript.sh
-rwxr-xr-x 1 student student 53 Sep 25 12:10 myscript.sh
$ ./myscript.sh
Hello World!ترفض المحاولة الأولى: فالأذونات -rw-r--r-- لا تحتوي على x. (وقد تظهر عندك للملف الجديد الأذونات -rw-rw-r-- بدلا منها؛ وفيها أيضا لا يوجد x.) ويضبط chmod 755 الأذونات على rwxr-xr-x: فللمالك القراءة والكتابة والتنفيذ، ولكل من سواه القراءة والتنفيذ. ويستخدم الاختصار 755 كثيرا مع السكربتات لأنه يتيح لك تعديل السكربت ويتيح للجميع تشغيله. وبعد ذلك يعمل السكربت ويطبع رسالته.
والطريقة الثانية لتشغيل سكربت أن تبدأ bash بنفسك وتعطيه اسم الملف وسيطا. وهي تعمل أيضا، لكنها تعني كتابة إضافية في كل مرة:
$ bash myscript.sh
Hello World!لماذا ./ في البداية؟
عندما تكتب اسما فقط، مثل ls، يبحث Bash عنه في قائمة من المجلدات مخزنة في المتغير PATH، ويشغل أول تطابق. ولا يبحث في المجلدات الفرعية، ولا يبحث في المجلد الحالي:
$ myscript.sh
bash: myscript.sh: command not found
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/binوالمجلدات مفصولة بالرمز :. وقد يحتوي PATH عندك على مجلدات أكثر، لكن المجلد الحالي ليس منها. ولهذا بضعة أسباب:
- يتيح لك الاحتفاظ بعدة إصدارات من برنامج واختيار الإصدار الذي يعمل بترتيب مكانه في القائمة.
- إنه مريح: يمكنك وضع سكربتاتك في مجلد موجود في
PATHوتشغيلها باسمها من أي مكان. - إنه أكثر أمانا: فقد ينشئ شخص ما سكربتا ضارا اسمه
lsفي مجلد ما؛ وما دام ذلك المجلد ليس فيPATHعندك، فلن تشغله كتابةlsمصادفة أبدا.
ولتشغيل برنامج ليس في PATH، أخبر Bash بمكانه بمسار (path) نسبي (relative) أو مطلق (absolute). والنقطة . تعني المجلد الحالي، ولذلك فإن ./myscript.sh مسار نسبي. والمسار المطلق يعمل بالطريقة نفسها:
$ /home/student/lab11/myscript.sh
Hello World!سطر الـshebang (#!)
يسمى الحرفان #! في بداية السطر الأول الـshebang. ويليهما مسار المفسر (interpreter)، وهو البرنامج الذي ينبغي أن يشغل بقية الملف. وهو في سكربت Bash المسار /bin/bash؛ أما الأنواع الأخرى من السكربتات فتذكر مفسرها الخاص.
والتنسيق مهم هنا:
- يجب أن يكون الـshebang في السطر الأول تماما من الملف (ولا يصلح السطر 2، حتى لو كان السطر 1 فارغا).
- يجب ألا توجد مسافات قبل
#ولا بين!والمسار. - استخدم المسار المطلق للمفسر، لأن السكربت قد يشغل من أي مجلد.
ويمكن حذف الـshebang: فإذا شغلت سكربتا بلا shebang من طرفية Bash، افترض Bash أنه سكربت Bash. لكن ذلك لا ينجح إلا إذا كان من يشغله داخل صدفة (shell) Bash، وهذا ليس الحال دائما، ولذلك ضع الـshebang دائما.
مثال محلول 2: shebang معطوب
السؤال. السكربت badbang.sh قابل للتنفيذ، لكن حرفا واحدا ناقص من مسار مفسره. توقع نتيجة ./badbang.sh ونتيجة bash badbang.sh.
#!/bin/bas
# the interpreter path is misspelled
echo Hello World!الإجابة.
$ ./badbang.sh
bash: ./badbang.sh: cannot execute: required file not found
$ bash badbang.sh
Hello World!يطلب ./badbang.sh من لينكس أن يبدأ المفسر المذكور في الـshebang، وهو /bin/bas، وهذا الملف غير موجود، فلا يمكن أن يبدأ السكربت. أما bash badbang.sh فيبدأ Bash مباشرة: فيصبح Bash هو المفسر، والسطر الأول عنده مجرد تعليق، فيعمل السكربت.
التعليقات
التعليق نص يتجاهله Bash؛ ويشرح الشيفرة لمن يقرؤها لاحقا. والرمز # يبدأ تعليقا يمتد إلى نهاية السطر. ولتعليق يمتد على عدة أسطر يمكنك استخدام : متبوعا بنص بين علامتي تنصيص مفردتين: فالأمر : لا يفعل شيئا، ولذلك يتجاهل النص المنصص. ويجب ألا يحتوي نص التعليق على علامة تنصيص مفردة خاصة به. وسترى النوعين في number.sh في الجزء 5.
التنسيق: المسافات مهمة
في مواضع كثيرة في Bash يكون وجود مسافة أو غيابها هو الفرق بين سطر يعمل وسطر لا يعمل. فقد بني Bash أولا لمستخدمين يكتبون في طرفية، ثم أضيفت البرمجة بالسكربتات لاحقا حول تلك القرارات. وكان الـshebang أول مثال؛ وستقابل أمثلة أخرى في هذا الدرس العملي. فاعتد ملاحظة المسافات عندما تقرأ الشيفرة.
الجزء 2: المتغيرات
كيف تعمل المتغيرات
المتغير مخزن مؤقت لمعلومة. وهناك أمران نفعلهما بالمتغير:
- تخزين قيمة:
name=value. بلا$عند التخزين، وبلا مسافة على أي من جانبي=. - قراءة القيمة: ضع
$قبل الاسم،$name، في أي موضع من السطر.
قبل أن ينفذ Bash سطرا، يبحث أولا عن أسماء المتغيرات فيه ويستبدل كل واحد منها بقيمته؛ ثم ينفذ السطر. ويمكن أن تكتب أسماء المتغيرات بحروف كبيرة أو صغيرة أو بخليط منهما، لكن Bash يميز بين الحروف الكبيرة والصغيرة (case sensitive)، فاكتب الاسم دائما بالطريقة نفسها. وأعط المتغيرات أسماء تصف محتواها.
مثال محلول 3: تخزين متغير وقراءته
السؤال. نكتب عند المحث هذه الأوامر الأربعة. توقع خرج كل منها: myvar = Hello، وmyvar=Hello، وecho $myvar، وecho $MYVAR.
الإجابة.
$ myvar = Hello
bash: myvar: command not found
$ myvar=Hello
$ echo $myvar
Hello
$ echo $MYVAR
مع وجود مسافات حول = يقرأ Bash الكلمة myvar على أنها أمر وسيطاه = وHello، ولا يوجد أمر بهذا الاسم. وبلا مسافات تخزن القيمة ويطبعها echo $myvar. أما $MYVAR فمتغير مختلف لم تخزن فيه قيمة قط، ولذلك يطبع echo سطرا فارغا.
المتغيرات في سكربت
احفظ باسم simplevariables.sh:
#!/bin/bash
# A simple variable example
myvariable=Hello
anothervar=Linux
echo $myvariable $anothervar
echo
sampledir=/etc/apt
ls $sampledir- السطران 3 و4 يخزنان قيمتي المتغيرين
myvariableوanothervar. - السطر 5 يطبع كليهما، للتحقق من تخزينهما.
- السطر 6 يشغل
echoبلا وسائط: وهي طريقة جيدة لطباعة سطر فارغ وتوزيع الخرج على مسافات مريحة. - السطر 7 يخزن في متغير مسار مجلد، والسطر 8 يشغل
lsمع قيمةsampledirوسيطا له.
$ ./simplevariables.sh
Hello Linux
apt.conf.d auth.conf.d keyrings preferences.d sources.list sources.list.d trusted.gpg.dاستخدم echo هنا فقط لإظهار أن القيم خزنت في المتغيرات؛ ولا تحتاج إلى echo لاستخدام متغير. والمتغيرات تسهل إدارة السكربتات أيضا: فإذا أشار سكربت إلى المجلد نفسه في عدة أوامر، فخزنه مرة واحدة في متغير، وإذا تغير المجلد عدلت سطرا واحدا.
علامات التنصيص
كانت كل قيمة حتى الآن كلمة واحدة. ويستخدم Bash المسافات للفصل بين العناصر، ولذلك تحتاج القيمة التي فيها مسافة إلى علامات تنصيص: فهي تخبر Bash أن المحتوى عنصر واحد. ويمكنك استخدام علامات التنصيص المفردة ' أو المزدوجة ":
- علامات التنصيص المفردة تعامل كل حرف حرفيا.
- علامات التنصيص المزدوجة تسمح بالاستبدال (substitution): فالمتغيرات داخلها تستبدل بقيمها.
مثال محلول 4: علامات التنصيص المفردة والمزدوجة
السؤال. توقع خرج كل سطر:
$ myvar=Hello World
$ myvar='Hello World'
$ echo $myvar
$ newvar="More $myvar"
$ echo $newvar
$ newvar='More $myvar'
$ echo $newvarالإجابة.
$ myvar=Hello World
bash: World: command not found
$ myvar='Hello World'
$ echo $myvar
Hello World
$ newvar="More $myvar"
$ echo $newvar
More Hello World
$ newvar='More $myvar'
$ echo $newvar
More $myvarبلا علامات تنصيص يرى Bash عنصرين، فيحاول تشغيل World أمرا. ومع علامات التنصيص تخزن Hello World كاملة. وداخل علامات التنصيص المزدوجة يستبدل $myvar بقيمته؛ أما داخل المفردة فيبقى النص $myvar. ولأن الأوامر تسلك السلوك نفسه عند المحث وفي السكربت، فالمحث مكان سريع للتجربة.
استبدال الأوامر
استبدال الأوامر (command substitution) يحفظ خرج أمر (أي ما كان سيطبعه على الشاشة) قيمة لمتغير. ضع الأمر داخل $( ):
$ myvar=$( ls /etc | wc -l )
$ echo There are $myvar entries in the directory /etc
There are 74 entries in the directory /etc
$ ls /etc/apt
apt.conf.d auth.conf.d keyrings preferences.d sources.list sources.list.d trusted.gpg.d
$ files=$( ls /etc/apt )
$ echo $files
apt.conf.d auth.conf.d keyrings preferences.d sources.list sources.list.d trusted.gpg.d74 هو عدد العناصر في /etc على جهاز الدرس العملي عندنا؛ وسيطبع نظامك عدده الخاص. وإذا امتد الخرج على عدة أسطر، يحتفظ المتغير بفواصل الأسطر (ولا يحذف إلا الفاصل الأخير). ويظهر echo $files الخرج في سطر واحد فقط لأن $files بلا علامات تنصيص: فيقسم Bash القيمة إلى كلمات، ويطبعها echo بمسافة واحدة بين كل كلمتين. أما echo "$files" فيطبعها في 7 أسطر. وعندما تستخدم استبدال الأوامر، اطبع المتغير بالأمر echo لتتحقق مما يحتويه فعلا.
وسائط سطر الأوامر
عندما تشغل ls -l /etc تكون الكلمتان -l و/etc وسيطي سطر أوامر للأمر ls. ويستقبل السكربت الوسائط بالطريقة نفسها: فالمتغير $1 يحمل الوسيط الأول، و$2 يحمل الثاني، وهكذا. والنظام يخزنها لك؛ وأنت تشير إليها فقط. احفظ باسم mycopy.sh:
#!/bin/bash
# A simple copy script
cp $1 $2
# Let's verify the copy worked
echo Details for $2
ls -lh $2- السطر 3 ينسخ الوسيط الأول (المصدر) إلى الثاني (الوجهة).
- السطر 5 يطبع رسالة، والسطر 6 يعرض الملف الجديد بالخيارين
-l(التفاصيل) و-h(حجم مقروء للإنسان) للتحقق من النسخ.
$ ./mycopy.sh salesdata.txt backup.txt
Details for backup.txt
-rw-r--r-- 1 student student 123 Sep 25 12:11 backup.txtمتغيرات خاصة أخرى
يخزن النظام بضعة متغيرات أخرى لكل سكربت:
| المتغير | يحمل |
|---|---|
$0 | اسم سكربت Bash |
$1 إلى $9 | أول 9 وسائط لسطر الأوامر |
$# | عدد الوسائط التي مررت إلى السكربت |
$@ | كل الوسائط الممررة إلى السكربت |
$? | حالة الخروج لآخر عملية شغلت |
$$ | معرف العملية (process ID) للسكربت الحالي |
$USER | اسم المستخدم الذي يشغل السكربت |
$HOSTNAME | اسم الجهاز (host name) الذي يعمل عليه السكربت |
$SECONDS | عدد الثواني منذ بدء السكربت |
$RANDOM | عدد عشوائي مختلف في كل مرة يشار إليه فيها |
$LINENO | رقم السطر الحالي في السكربت |
ويعرض الأمر env متغيرات أخرى كثيرة يمكنك الإشارة إليها.
مثال محلول 5: ماذا استقبل السكربت؟
السؤال. احفظ args.sh وتوقع خرج ./args.sh red green blue، ثم خرج ./args.sh بلا وسائط.
#!/bin/bash
# special variables set by the system
echo "Script name: $0"
echo "Number of arguments: $#"
echo "All arguments: $@"
echo "Second argument: $2"
echo "User: $USER, host: $HOSTNAME"
echo "This is line $LINENO"الإجابة.
$ ./args.sh red green blue
Script name: ./args.sh
Number of arguments: 3
All arguments: red green blue
Second argument: green
User: student, host: ubuntu-lab
This is line 8
$ ./args.sh
Script name: ./args.sh
Number of arguments: 0
All arguments:
Second argument:
User: student, host: ubuntu-lab
This is line 8$0 هو اسم السكربت كما كتب، ./args.sh. ومع ثلاثة وسائط تكون قيمة $# هي 3، ويعرض $@ الوسائط كلها، و$2 هو green. وبلا وسائط تكون قيمة $# هي 0، ويكون $@ و$2 فارغين. وقيمة $LINENO هي 8 لأن الأمر echo في السطر 8.
تصدير المتغيرات
تذكر أن السكربت يعمل في عمليته الخاصة. والمتغيرات تنتمي إلى العملية التي أنشأتها؛ ويسمى هذا النطاق (scope). وعندما يشغل سكربت سكربتا آخر، يكون السكربت الثاني عملية جديدة لا ترى متغيرات الأول، إلا إذا صدرها (export) الأول.
مثال محلول 6: export والنطاق
السؤال. يخزن script1.sh قيمتين في متغيرين، ويصدر أحدهما فقط، ثم يشغل script2.sh الذي يغير الاثنين. توقع الأسطر الثلاثة التي يطبعها ./script1.sh.
#!/bin/bash
# demonstrate variable scope 1.
var1=blah
var2=foo
# Let's verify their current value
echo $0 :: var1 : $var1, var2 : $var2
export var1
./script2.sh
# Let's see what they are now
echo $0 :: var1 : $var1, var2 : $var2#!/bin/bash
# demonstrate variable scope 2
# Let's verify their current value
echo $0 :: var1 : $var1, var2 : $var2
# Let's change their values
var1=flop
var2=blehالإجابة.
$ ./script1.sh
./script1.sh :: var1 : blah, var2 : foo
./script2.sh :: var1 : blah, var2 :
./script1.sh :: var1 : blah, var2 : fooيقول الأمر export var1 لـBash: كلما أنشئت عملية جديدة، أعطها نسخة من var1. ولذلك يرى script2.sh المتغير var1 (blah) ولا يرى var2 (فارغ). والنسخة متغير منفصل في عملية منفصلة: فعندما يغيرها script2.sh إلى flop، يبقى الأصل في script1.sh هو blah. والتصدير يعمل في اتجاه واحد، من عملية إلى العمليات التي تبدؤها.
الجزء 3: الإدخال
وسائط سطر الأوامر طريقة واحدة لإعطاء البيانات لسكربت. وهذه طريقتان أخريان: سؤال المستخدم أثناء عمل السكربت، وقراءة البيانات الممررة إلى السكربت عبر أنبوب.
اسأل المستخدم بالأمر read
يأخذ read سطرا من إدخال المستخدم ويحفظه في متغير. احفظ باسم introduction.sh:
#!/bin/bash
# Ask the user for their city
echo Hello, which city are you from?
read varcity
echo It\'s nice to meet someone from $varcity- السطر 3 يطبع سؤالا.
- السطر 4 يشغل
read، الذي ينتظر أن يكتب المستخدم سطرا ويضغط Enter، ثم يحفظه فيvarcity. - السطر 5 يطبع رسالة للتحقق من نجاح
read. والشرطة المائلة العكسية (backslash) فيIt\'sتهرب (escape) علامة التنصيص، فتطبع علامة'عادية بدلا من أن تبدأ نصا منصصا.
$ ./introduction.sh
Hello, which city are you from?
Mansoura
It's nice to meet someone from Mansoura(والنص المكتوب هو Mansoura.)
read -p وread -s
يغير خياران مفيدان سلوك read: الخيار -p يعرض مطالبة، أي نصا يظهر قبل الإدخال، والخيار -s يجعل الإدخال صامتا (لا يظهر شيء أثناء الكتابة). ويفيدان معا في إدخال اسم مستخدم وكلمة مرور. احفظ باسم login.sh:
#!/bin/bash
# Ask the user for login details
read -p 'Username: ' uservar
read -sp 'Password: ' passvar
echo
echo Thank you $uservar, we now have your login detailsووضعت المطالبتان بين علامتي تنصيص لكي تنتهيا بمسافة؛ وإلا لبدأ النص المكتوب بعد النقطتين مباشرة.
$ ./login.sh
Username: student
Password:
Thank you student, we now have your login detailsجرت كتابة كلمة المرور لكنها لم تظهر قط. ولأن -s يخفي أيضا ضغطة Enter، كان المؤشر سيبقى على السطر Password:، ولذلك يشغل السطر 5 الأمر echo بلا وسائط للانتقال إلى سطر جديد.
عدة متغيرات في read واحد
يمكنك أن تعطي read عدة أسماء متغيرات. فيقسم السطر عند المسافات: الكلمة الأولى للمتغير الأول، والثانية للثاني، وهكذا. احفظ باسم cars.sh:
#!/bin/bash
# Demonstrate how read actually works
echo What cars do you like?
read car1 car2 car3
echo Your first car was: $car1
echo Your second car was: $car2
echo Your third car was: $car3مثال محلول 7: كلمات أكثر من المتغيرات
السؤال. يكتب المستخدم Jaguar Maserati Bentley في التشغيل الأول، وJaguar Maserati Bentley Lotus في الثاني، وJaguar Maserati في الثالث. توقع كل خرج.
الإجابة.
$ ./cars.sh
What cars do you like?
Jaguar Maserati Bentley
Your first car was: Jaguar
Your second car was: Maserati
Your third car was: Bentley
$ ./cars.sh
What cars do you like?
Jaguar Maserati Bentley Lotus
Your first car was: Jaguar
Your second car was: Maserati
Your third car was: Bentley Lotus
$ ./cars.sh
What cars do you like?
Jaguar Maserati
Your first car was: Jaguar
Your second car was: Maserati
Your third car was:مع ثلاث كلمات بالضبط يأخذ كل متغير كلمة. وإذا كانت الكلمات أكثر من المتغيرات، ذهبت كل الكلمات الباقية إلى المتغير الأخير، car3. وإذا كانت أقل، بقيت المتغيرات الباقية فارغة.
القراءة من STDIN
يتيح لك لينكس أن تربط أوامر بسيطة معا بالأنابيب لتكوين حل أكبر، ويمكن للسكربت أن ينضم إلى خط أنابيب (pipeline) أيضا. ولكل عملية ثلاثة ملفات خاصة: STDIN (الإدخال)، وSTDOUT (الخرج)، وSTDERR (الأخطاء)، ويوصلها لينكس عندما تستخدم أنبوبا أو إعادة توجيه (redirection). واختصاراتها /dev/stdin و/dev/stdout و/dev/stderr. ولمعالجة البيانات الممررة إلى السكربت عبر أنبوب، يقرأ السكربت /dev/stdin كأي ملف عادي. احفظ باسم summary.sh:
#!/bin/bash
# A basic summary of my sales report
echo Here is a summary of the sales data:
echo ====================================
echo
cat /dev/stdin | cut -d' ' -f 2,3 | sort- الأسطر من 3 إلى 5 تطبع عنوانا.
- السطر 6 يقرأ STDIN بالأمر
cat، ويبقي الحقلين 2 و3 بالأمرcut(والفاصل مسافة)، ثم يرتب النتيجة.
$ cat salesdata.txt
stall1 apples 20 September 4
stall2 oranges 5 September 7
stall3 watermelons 12 September 10
stall4 peaches 7 September 15
$ cat salesdata.txt | ./summary.sh
Here is a summary of the sales data:
====================================
apples 20
oranges 5
peaches 7
watermelons 12وإعادة توجيه الملف إلى السكربت تعطي النتيجة نفسها:
$ ./summary.sh < salesdata.txt
Here is a summary of the sales data:
====================================
apples 20
oranges 5
peaches 7
watermelons 12أي طريقة أستخدم؟
| الطريقة | الأفضل عندما |
|---|---|
| وسائط سطر الأوامر | في معظم الحالات: سهلة على المستخدمين، وتحفظ في سجل الأوامر، ويسهل على السكربتات الأخرى (أو على مهمة مجدولة) استدعاؤها |
read أثناء عمل السكربت | يجب ألا تبقى البيانات في سجل أوامر أي أحد، مثل بيانات تسجيل الدخول |
| STDIN | السكربت يعالج البيانات فقط، فيمكن وضعه في خط أنابيب |
ويمكنك الجمع بينها، مثل اسم ملف وسيطا، وإذا غاب فالبيانات من STDIN (ويبين الجزء 5 كيف). وفكر في ثلاثة أمور: سهولة الاستخدام، وأمان البيانات الحساسة، والمتانة (robustness): أي أن تجعل استخدام السكربت بطريقة خاطئة صعبا.
الجزء 4: العمليات الحسابية
هناك عدة طرق لإجراء العمليات الحسابية في Bash. نتناولها كلها، لكن الطريقة الموصى بها هي الأخيرة، الأقواس المزدوجة (double parentheses). والمعاملات الشائعة واحدة في كل الطرق:
| المعامل | العملية |
|---|---|
+، -، *، / | الجمع، والطرح، والضرب، والقسمة |
var++ | زيادة المتغير var بمقدار 1 |
var-- | إنقاص المتغير var بمقدار 1 |
% | باقي القسمة (modulus): الباقي بعد القسمة |
الأمر let
الأمر let مدمج في Bash. وجزؤه الأول عادة هو المتغير الذي تحفظ فيه النتيجة: let a=5+4. احفظ باسم let_example.sh:
#!/bin/bash
# Basic arithmetic using let
let a=5+4
echo $a
let "a = 5 + 4"
echo $a
let a++
echo $a
let "a = 4 * 5"
echo $a
let "a = $1 + 30"
echo $aمثال محلول 8: let
السؤال. توقع خرج ./let_example.sh 15.
الإجابة.
$ ./let_example.sh 15
9
9
10
20
45- السطر 3 هو الصيغة الأساسية: بلا علامات تنصيص يجب أن يكون التعبير بلا مسافات.
- السطر 5 يستخدم علامات التنصيص، وهي تسمح بالمسافات لتسهيل القراءة.
- السطر 7،
a++، يزيدaبمقدار 1: فتصبح9هي10. - السطر 11 يستخدم متغيرا آخر:
$1هو15، ولذلكa = 15 + 30 = 45.
الأمر expr
الأمر expr يشبه let، لكنه بدلا من حفظ النتيجة في متغير يطبعها. ويجب أن تضع مسافات بين العناصر، ويجب ألا تستخدم علامات التنصيص. ومن الشائع وضع expr داخل استبدال الأوامر لحفظ خرجه. احفظ باسم expr_example.sh:
#!/bin/bash
# Basic arithmetic using expr
expr 5 + 4
expr "5 + 4"
expr 5+4
expr 5 \* $1
expr 11 % 2
a=$( expr 10 - 3 )
echo $aمثال محلول 9: expr
السؤال. توقع خرج ./expr_example.sh 12.
الإجابة.
$ ./expr_example.sh 12
9
5 + 4
5+4
60
1
7- السطر 3 هو الصيغة الأساسية، ويطبع
9. - السطر 4: مع علامات التنصيص لا يحسب التعبير، بل يطبع فقط.
- السطر 5: بلا مسافات لا يحسب التعبير أيضا.
- السطر 6: للرمز
*معنى خاص عند Bash، ولذلك يهرب بشرطة مائلة عكسية:5 * 12 = 60. - السطر 7: باقي القسمة، أي باقي قسمة
11على2، هو1. - السطر 8 يحفظ خرج
exprفيaباستبدال الأوامر.
ما الخطأ الذي يحدث بلا الشرطة المائلة العكسية؟ قبل أن يعمل expr يستبدل Bash الرمز * غير المهرب بأسماء الملفات في المجلد الحالي، فيستقبل expr أسماء ملفات بدلا من معامل:
$ expr 5 * 4
expr: syntax error: unexpected argument ‘args.sh’
$ expr 5 \* 4
20وتذكر رسالة الخطأ أول ملف، بالترتيب الأبجدي، في مجلدك أنت. فإذا أنشأت سكربتات هذه الصفحة بترتيبها، فهو args.sh.
الأقواس المزدوجة
تستخدم الطريقة الموصى بها الأقواس المزدوجة، وهي تشبه استبدال الأوامر مع زوج إضافي من الأقواس: $(( expression )) يرجع النتيجة. احفظ باسم expansion2.sh؛ والرمز ; يفصل بين أمرين في سطر واحد:
#!/bin/bash
# Basic arithmetic using double parentheses
a=$(( 4 + 5 )); echo $a
a=$((3+5)); echo $a
b=$(( a + 3 )); echo $b
b=$(( $a + 4 )); echo $b
(( b++ )); echo $b
(( b += 3 )); echo $b
a=$(( 4 * 5 )); echo $aمثال محلول 10: الأقواس المزدوجة
السؤال. توقع الأعداد السبعة التي يطبعها ./expansion2.sh.
الإجابة.
$ ./expansion2.sh
9
8
11
12
13
16
20- السطر 3 هو الصيغة الأساسية؛ ويمكنك توزيع المسافات في التعبير بحرية بلا علامات تنصيص.
- السطر 4 يبين أنها تعمل بالطريقة نفسها بلا أي مسافات.
- السطر 5 يستخدم متغيرا بلا الرمز
$، والسطر 6 معه؛ وكلاهما يعمل:8 + 3 = 11، ثم8 + 4 = 12. - السطر 7،
(( b++ ))، يزيدbبمقدار 1 لتصبح13. وهذه الصيغة تغير المتغير مباشرة، فلا حاجة إلى$قبل الأقواس. - السطر 8،
(( b += 3 ))، اختصار للتعبيرb = b + 3:16. - السطر 9 يضرب بلا تهريب الرمز
*:20.
والأقواس المزدوجة مرنة في طريقة تنسيقها، وهذا جزء من سبب تفضيلها؛ ولأنها مدمجة في Bash فهي أسرع قليلا أيضا. والنتيجة دائما عدد صحيح: فيحذف أي كسر. ويطبق ترتيب العمليات المعتاد، والأقواس تغيره:
$ echo $(( 7 / 2 )) $(( 7 % 2 ))
3 1
$ echo $(( 2 * 3 + 4 )) $(( 2 * (3 + 4) ))
10 14مثال آخر: العمليات الحسابية خطوة بخطوة
السؤال. احفظ 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-decrement
((--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"الإجابة.
$ ./arith.sh
sum = 75
division = 2
Value after decrement = 9
Value after adding 10 = 1950 + 25 = 75 و50 / 25 = 2. والتعبير ((--N)) ينقص N بمقدار 1 قبل استخدامه: فتصبح 10 هي 9. ثم يعطي (( N += 10 )) القيمة 19.
طول متغير
هذا ليس عملية حسابية في الحقيقة، لكنه مفيد. فهذه الصيغة تعطي عدد الأحرف في قيمة متغير:
${#variable}احفظ باسم length_example.sh:
#!/bin/bash
# Show the length of a variable.
a='Hello World'
echo ${#a}
b=4953
echo ${#b}$ ./length_example.sh
11
4عدد أحرف Hello World هو 11 (فالمسافة تحسب)، وعدد أحرف 4953 هو 4.
الجزء 5: جمل if
تتيح جمل if للسكربت أن يتخذ القرارات: أي أن ينفذ جزءا من الشيفرة فقط عندما يكون شرط ما صحيحا. وصياغتها محددة جدا، فانتبه لكل تفصيل.
جملة if الأساسية
if [ some test ]
then
commands
fiكل ما بين then وfi (كلمة if مقلوبة) ينفذ فقط إذا كان الاختبار داخل الأقواس المربعة صحيحا. احفظ باسم if_example.sh:
#!/bin/bash
# Basic if statement
if [ $1 -gt 100 ]
then
echo Hey that\'s a large number.
pwd
fi
date- السطر 3: هل وسيط سطر الأوامر الأول أكبر من 100؟
- السطران 5 و6 ينفذان فقط إذا كان ذلك الاختبار صحيحا. ويمكن أن تضع هنا أي عدد تريده من الأوامر. والشرطة المائلة العكسية تتيح للأمر
echoطباعة'. - السطر 7،
fi، ينهي جملةif. والسطر 8 خارجها، ولذلك يعملdateأيا كانت النتيجة.
$ ./if_example.sh 15
Fri Sep 25 12:13:04 PM UTC 2026
$ ./if_example.sh 150
Hey that's a large number.
/home/student/lab11
Fri Sep 25 12:13:05 PM UTC 2026واختبر السكربت دائما بإدخال يغطي كل حالة ممكنة، وهنا عدد صغير وعدد كبير.
الأمر test
الأقواس المربعة [ ] هي في الحقيقة الأمر test. ولذلك يمكن استخدام كل معاملات test داخلها. ولأن [ أمر، فهو يحتاج إلى مسافة بعده مثل أي أمر آخر:
$ [5 -lt 12]
bash: [5: command not foundأشهر المعاملات (والقائمة الكاملة في man test):
| المعامل | يكون صحيحا عندما |
|---|---|
! EXPRESSION | يكون التعبير EXPRESSION خطأ |
-n STRING | يكون طول النص STRING أكبر من صفر |
-z STRING | يكون طول النص STRING صفرا (أي أنه فارغ) |
STRING1 = STRING2 | يكون النص STRING1 مساويا للنص STRING2 |
STRING1 != STRING2 | يكون النص STRING1 غير مساو للنص STRING2 |
INTEGER1 -eq INTEGER2 | يكون العدد INTEGER1 مساويا للعدد INTEGER2 عدديا |
INTEGER1 -gt INTEGER2 | يكون العدد INTEGER1 أكبر من العدد INTEGER2 عدديا |
INTEGER1 -lt INTEGER2 | يكون العدد INTEGER1 أصغر من العدد INTEGER2 عدديا |
INTEGER1 -ge INTEGER2 | يكون العدد INTEGER1 أكبر من العدد INTEGER2 أو يساويه عدديا |
INTEGER1 -le INTEGER2 | يكون العدد INTEGER1 أصغر من العدد INTEGER2 أو يساويه عدديا |
-d FILE | يكون FILE موجودا وهو مجلد |
-e FILE | يكون FILE موجودا |
-r FILE | يكون FILE موجودا وإذن القراءة ممنوحا |
-s FILE | يكون FILE موجودا وحجمه أكبر من صفر (أي أنه ليس فارغا) |
-w FILE | يكون FILE موجودا وإذن الكتابة ممنوحا |
-x FILE | يكون FILE موجودا وإذن التنفيذ ممنوحا |
وFILE هنا تعني مسارا، مطلقا أو نسبيا، إلى ملف أو مجلد. وكل مثال من هذه الأمثلة صحيح (حالة الخروج 0، ويأتي شرحها بعد قليل)، ما عدا المثالين الأخيرين:
$ [ 5 -eq 5 ]
$ echo $?
0
$ [ 9 -gt 5 ]
$ echo $?
0
$ [ 3 -lt 8 ]
$ echo $?
0
$ [ 7 -ge 7 ]
$ echo $?
0
$ [ 4 -le 6 ]
$ echo $?
0
$ x=hello
$ [ -n "$x" ]
$ echo $?
0
$ [ -z "$x" ]
$ echo $?
1
$ [ -d /etc ]
$ echo $?
0
$ [ -x myscript.sh ]
$ echo $?
0
$ [ -w /etc/passwd ]
$ echo $?
1$x ليس فارغا، ولذلك فالاختبار -z خطأ؛ والمستخدم العادي لا يسمح له بالكتابة في /etc/passwd، ولذلك فالاختبار -w خطأ.
الأمر test وحالة الخروج $?
الأمر test لا يطبع شيئا. بل يجيب عبر حالة خروجه، التي يحملها المتغير $? مباشرة بعد الأمر: 0 تعني صحيح (نجاح)، و1 تعني خطأ (فشل). ولأن [ ] ليس إلا test، يمكنك تجربة اختبار عند المحث قبل وضعه في سكربت.
مثال محلول 11: = مقابل -eq
السؤال. توقع كل echo $?:
$ test 001 = 1
$ test 001 -eq 1
$ touch myfile
$ test -s myfile
$ ls /etc > myfile
$ test -s myfileالإجابة.
$ test 001 = 1
$ echo $?
1
$ test 001 -eq 1
$ echo $?
0
$ touch myfile
$ test -s myfile
$ echo $?
1
$ ls /etc > myfile
$ test -s myfile
$ echo $?
0المعامل = مقارنة نصوص: 001 و1 ليسا الأحرف نفسها، ولذلك فالنتيجة خطأ (1). والمعامل -eq مقارنة أعداد: 001 هو العدد 1، ولذلك فالنتيجة صحيح (0). وينشئ touch الملف myfile فارغا، ولذلك فالشرط "الحجم أكبر من صفر" خطأ؛ وبعد إعادة توجيه خرج ls /etc إليه يصبح في الملف محتوى ويكون الاختبار صحيحا.
وإذا تلقى معامل عددي شيئا ليس عددا صحيحا، يطبع test رسالة خطأ ويرجع 2:
$ test abc -eq 1
bash: test: abc: integer expression expected
$ echo $?
2جرب أي زوج من القيم مع كل المعاملات في الأداة التفاعلية: افتح أداة الأمر test بملء الشاشة.
الإزاحة
في السكربتات أعلاه، الأوامر داخل if مزاحة (indented) إلى الداخل. ولا يشترط Bash ذلك، ويعمل السكربت بالطريقة نفسها تماما بدونها، لكن الإزاحة تجعل البنية سهلة الرؤية وتجعل الأخطاء البسيطة أصعب وقوعا. وتزداد أهميتها كلما كبرت السكربتات.
if وelse
لتنفيذ مجموعة من الأوامر عندما يكون الاختبار صحيحا ومجموعة أخرى عندما يكون خطأ، أضف else:
if [ some test ]
then
commands
else
other commands
fiاحفظ باسم var.sh. وهو يقارن عددا يكتبه المستخدم بالمتغير b:
#!/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$ ./var.sh
Enter a number
56
Numbers are not equal
$ ./var.sh
Enter a number
100
Numbers are equalيستخدم هذا السكربت الأقواس المربعة المزدوجة [[ ]]. وهي تقبل المعاملات نفسها، وستقابلها في سكربتات كثيرة.
وتتيح else أيضا للسكربت أن يأخذ بياناته من ملف أو من STDIN. احفظ باسم else.sh: فإذا أعطي وسيط واحد بالضبط، رقم nl أسطر ذلك الملف؛ وإلا رقم ما يصل عبر STDIN.
#!/bin/bash
# else example
if [ $# -eq 1 ]
then
nl $1
else
nl /dev/stdin
fi$ ./else.sh salesdata.txt
1 stall1 apples 20 September 4
2 stall2 oranges 5 September 7
3 stall3 watermelons 12 September 10
4 stall4 peaches 7 September 15if وelif وelse
عندما توجد عدة احتمالات، اختبرها واحدا بعد الآخر بالكلمة elif (أي else if):
if [ some test ]
then
commands
elif [ some other test ]
then
different commands
else
other commands
fiيفحص Bash الاختبارات بالترتيب، وينفذ أول فرع (branch) يكون اختباره صحيحا فقط. ويمكنك أن تضع أي عدد تريده من فروع elif، وelse الأخيرة اختيارية.
مثال محلول 12: الحفلة
إذا كان عمرك 18 عاما أو أكثر فيمكنك الذهاب إلى الحفلة. وإذا لم يكن كذلك، لكن معك رسالة من والديك (yes)، فيمكنك الذهاب بشرط أن تعود قبل منتصف الليل. وإلا فلا يمكنك الذهاب. احفظ باسم if_elif.sh:
#!/bin/bash
# elif statements
if [ $1 -ge 18 ]
then
echo You may go to the party.
elif [ $2 == 'yes' ]
then
echo You may go to the party but be back before midnight.
else
echo You may not go to the party.
fiالسؤال. توقع ./if_elif.sh 20 no و./if_elif.sh 16 yes و./if_elif.sh 16 no.
الإجابة.
$ ./if_elif.sh 20 no
You may go to the party.
$ ./if_elif.sh 16 yes
You may go to the party but be back before midnight.
$ ./if_elif.sh 16 no
You may not go to the party.20 -ge 18 صحيح، ولذلك لا يفحص الوسيط الثاني أبدا. ويفشل 16 في الاختبار الأول؛ ثم يكون yes == 'yes' صحيحا في التشغيل الثاني وخطأ في الثالث، فينتقل التنفيذ إلى else.
مثال آخر: أصغر من، أو أكبر من، أو يساوي
يستخدم number.sh نوعي التعليق من الجزء 1، وسلسلة if وelif وelse:
#!/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السؤال. توقع الخرج للإدخالات 3 و10 و90.
الإجابة.
$ ./number.sh
Enter a number
3
The number is less than 10
$ ./number.sh
Enter a number
10
The number is equal to 10
$ ./number.sh
Enter a number
90
The number is greater than 103 -lt 10 صحيح، ولذلك ينفذ الفرع الأول. و10 ليس أصغر من 10 ولا أكبر منه، فيفشل الاختباران وتنفذ else. و90 -gt 10 يجعل فرع elif ينفذ.
جمل if المتداخلة
يمكن أن توضع جملة if داخل جملة if أخرى. والإزاحة تبين بوضوح أي fi يغلق أي if. احفظ باسم nested_if.sh:
#!/bin/bash
# Nested if statements
if [ $1 -gt 100 ]
then
echo Hey that\'s a large number.
if (( $1 % 2 == 0 ))
then
echo And is also an even number.
fi
fi- السطر 3: استمر فقط إذا كان الوسيط الأول أكبر من 100.
- السطر 6 صيغة بديلة: لاختبار تعبير حسابي استخدم الأقواس المزدوجة، كما في العمليات الحسابية.
- السطر 8 ينفذ فقط إذا كان الاختباران كلاهما صحيحين.
مثال محلول 13: اختبارات متداخلة
السؤال. توقع الخرج للأعداد 150 و151 و50.
الإجابة.
$ ./nested_if.sh 150
Hey that's a large number.
And is also an even number.
$ ./nested_if.sh 151
Hey that's a large number.
$ ./nested_if.sh 50150 كبير وزوجي (150 % 2 يساوي 0). و151 كبير لكنه فردي. و50 يفشل في الاختبار الخارجي، ولذلك لا يطبع شيء على الإطلاق. ويمكنك التداخل إلى أي عمق تحتاج إليه، لكن تجاوز 3 مستويات علامة على أن المنطق يحتاج إلى إعادة تنظيم.
العمليات المنطقية: && و||
أحيانا ينبغي أن يحدث إجراء فقط إذا كانت عدة شروط صحيحة، أو إذا كان أي واحد منها صحيحا:
- و (and):
&&صحيح عندما يكون الاختباران صحيحين كلاهما. - أو (or):
||صحيح عندما يكون اختبار واحد على الأقل صحيحا.
مع الأقواس المفردة يأخذ كل اختبار أقواسه الخاصة. احفظ باسم and.sh: فالملف المعطى وسيطا "مفيد" فقط إذا كان قابلا للقراءة وغير فارغ.
#!/bin/bash
# and example
if [ -r $1 ] && [ -s $1 ]
then
echo This file is useful.
fi$ touch empty.txt
$ ./and.sh empty.txt
$ ./and.sh salesdata.txt
This file is useful.ومع الأقواس المزدوجة [[ ]] يمكن أن يوضع && داخل الأقواس. ويتحقق cond.sh من أن العمر 18 على الأقل وأن الرمز 1100:
#!/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مثال محلول 14: && مع read
السؤال. توقع النتيجة للرمز 1100 مع العمر 5، والرمز 1100 مع العمر 45، والرمز 1200 مع العمر 45.
الإجابة.
$ ./cond.sh
Enter your code
1100
Enter your age
5
You are not eligible to see the movie
$ ./cond.sh
Enter your code
1100
Enter your age
45
You are eligible to see the movie
$ ./cond.sh
Enter your code
1200
Enter your age
45
You are not eligible to see the movieالتشغيل الثاني وحده ينجح في الاختبارين كليهما. فالأول يفشل في اختبار العمر، والثالث يفشل في اختبار الرمز؛ ومع && يكفي اختبار واحد خطأ للوصول إلى else.
جمل case
عندما يجب مطابقة متغير مع قائمة من الأنماط (patterns)، تصبح سلسلة if وelif صعبة القراءة. وجملة case أوضح. احفظ باسم case2.sh:
#!/bin/bash
# case example
case $1 in
start) echo starting ;;
stop) echo stopping ;;
restart) echo restarting ;;
*) echo don\'t know ;;
esac- السطر 3 يبدأ
caseعلى قيمة$1. - ينتهي كل نمط بالرمز
)، وتليه أوامره، وينتهي كل فرع بالرمز;;. - النمط
*يطابق أي شيء: فهو فرع يلتقط كل ما لم يطابقه نمط آخر. esac(كلمةcaseمقلوبة) تنهي الجملة.
$ ./case2.sh start
starting
$ ./case2.sh stop
stopping
$ ./case2.sh restart
restarting
$ ./case2.sh pause
don't knowحالة خروج السكربت
كل أمر، وكل سكربت، ينتهي بحالة خروج، ويحملها $?. ويحدد السكربت حالة خروجه بالأمر exit: exit 0 للنجاح، وexit 1 للفشل. ثم يستطيع سكربت آخر أن يشغله ويختبر $?. احفظ باسم first.sh:
#!/bin/bash
echo "Enter a numeric value"
read n
# Check the input value is less than or equal to 100 or not
if [[ $n -le 100 ]]
then
exit 0
else
exit 1
fiثم احفظ باسم second.sh؛ وهو يشغل first.sh ويختبر حالة خروجه:
#!/bin/bash
#Execute the file, first.sh
bash "first.sh"
#Check the return code is equal to 1 or not
if [ $? -eq 1 ]
then
echo "The input number is greater than 100"
else
echo "The input number is less than or equal to 100"
fi$ ./second.sh
Enter a numeric value
55
The input number is less than or equal to 100
$ ./second.sh
Enter a numeric value
110
The input number is greater than 100يشغل second.sh السكربت first.sh بالأمر bash، ولذلك لا يحتاج first.sh حتى إلى إذن التنفيذ. ويخرج first.sh بالحالة 0 للإدخال 55 وبالحالة 1 للإدخال 110، ويقرأ second.sh تلك القيمة من $? مباشرة بعد ذلك.
شاهده يعمل سطرا بسطر
تشغل أداة التتبع خطوة بخطوة ثلاثة سكربتات صغيرة (تقدير من الدرجة، وآلة حاسبة على وسيطين، وتحقق من حساب باستخدام read -s). تقدم سطرا واحدا في كل مرة وراقب تغير المتغيرات والإدخال والطرفية؛ ويشرح كل اختبار [ ] عند تنفيذه. وخرجها يطابق Bash الحقيقي تماما. افتح أداة التتبع بملء الشاشة.
تمارين
نحو 30 دقيقة. حل التمرينين 1 و2 على الورق أولا، ثم تحقق منهما في الطرفية. والتمرينان 3 و4 سكربتان تكتبهما وتحفظهما وتجعلهما قابلين للتنفيذ وتختبرهما. والتمرين 5 لمن ينتهي مبكرا. والإجابات في نهاية الصفحة.
التمرين 1: توقع الخرج (نحو 5 دقائق)
اكتب الأسطر الخمسة التي يطبعها ./practice1.sh:
#!/bin/bash
a=7
b=3
echo '$a + $b'
echo "$a + $b"
echo $(( a + b )) $(( a / b )) $(( a % b ))
expr $a \* $b
let "c = a * b - 1"
echo $c ${#c}التمرين 2: صحيح أم خطأ؟ (نحو 5 دقائق)
الملف nofile.txt غير موجود. توقع قيمة echo $? بعد كل أمر:
test 5 -lt 12test abc = ABCtest 010 -eq 10test 010 = 10test -z ""test -e nofile.txt
التمرين 3: العدد الأكبر (نحو 8 دقائق)
اكتب larger.sh. وهو يأخذ عددين وسيطين في سطر الأوامر ويطبع The larger number is N. وإذا كان العددان متساويين يطبع Both numbers are N. اختبره بالأوامر ./larger.sh 17 42 و./larger.sh 42 17 و./larger.sh 9 9.
التمرين 4: فاتورة متجر (نحو 10 دقائق)
اكتب bill.sh:
- اسأل بالأمر
read -pعن سعر القطعة الواحدة وعن الكمية. - احسب الإجمالي بالصيغة
$(( ))واطبعTotal: T. - إذا كان الإجمالي 500 أو أكثر، يحصل الزبون على خصم 10%: فاطبع
Discount: D(الإجمالي مقسوما على 10) وTo pay: P. - وإلا فاطبع
No discount, to pay: T.
اختبره بالسعر 120 والكمية 3، ثم بالسعر 200 والكمية 3.
التمرين 5 (إذا انتهيت مبكرا): ما هذا المسار؟
اكتب filecheck.sh. وهو يأخذ مسارا واحدا وسيطا في سطر الأوامر ويطبع سطرا واحدا:
- بلا وسيط:
Usage:متبوعا باسم السكربت ثمpath - إذا لم يكن المسار موجودا:
PATH does not exist - إذا كان مجلدا:
PATH is a directory - إذا كان ملفا قابلا للتنفيذ:
PATH is an executable file - وإلا:
PATH is a file, not executable
اختبره بلا وسيط، ثم مع nofile.txt و/etc وbill.sh وsalesdata.txt.
الإجابات
الإجابة 1
$ ./practice1.sh
$a + $b
7 + 3
10 2 1
21
20 2- داخل علامات التنصيص المفردة لا يستبدل شيء، ولذلك فالسطر الأول هو النص الحرفي.
- داخل علامات التنصيص المزدوجة تستبدل القيم:
7 + 3، لكن لا يحسب شيء. 7 + 3 = 10؛ و7 / 3يساوي2لأن الكسر يحذف؛ والباقي7 % 3يساوي1.- يطبع
exprمع*المهرب القيمة21. - يحسب
letالقيمةc = 7 * 3 - 1 = 20، وعدد أحرف القيمة20هو 2.
الإجابة 2
$ test 5 -lt 12
$ echo $?
0
$ test abc = ABC
$ echo $?
1
$ test 010 -eq 10
$ echo $?
0
$ test 010 = 10
$ echo $?
1
$ test -z ""
$ echo $?
0
$ test -e nofile.txt
$ echo $?
15 أصغر من 12 (صحيح). وabc وABC نصان مختلفان، لأن Bash يميز بين الحروف الكبيرة والصغيرة (خطأ). ويقرأ -eq القيمة 010 على أنها العدد 10 (صحيح)، لكن = يقارن النص 010 بالنص 10 (خطأ). والنص الفارغ يجعل -z صحيحا. والملف غير موجود، ولذلك فالاختبار -e خطأ.
الإجابة 3
#!/bin/bash
# print the larger of two numbers
if [ $1 -gt $2 ]
then
echo "The larger number is $1"
elif [ $1 -lt $2 ]
then
echo "The larger number is $2"
else
echo "Both numbers are $1"
fi$ ./larger.sh 17 42
The larger number is 42
$ ./larger.sh 42 17
The larger number is 42
$ ./larger.sh 9 9
Both numbers are 9الإجابة 4
#!/bin/bash
# a shop bill with a discount
read -p "Price of one item: " price
read -p "Quantity: " qty
total=$(( price * qty ))
echo "Total: $total"
if [ $total -ge 500 ]
then
discount=$(( total / 10 ))
echo "Discount: $discount"
echo "To pay: $(( total - discount ))"
else
echo "No discount, to pay: $total"
fi$ ./bill.sh
Price of one item: 120
Quantity: 3
Total: 360
No discount, to pay: 360
$ ./bill.sh
Price of one item: 200
Quantity: 3
Total: 600
Discount: 60
To pay: 540120 * 3 = 360 أقل من 500، ولذلك لا يوجد خصم. و200 * 3 = 600: فالخصم 600 / 10 = 60، ويدفع الزبون 600 - 60 = 540.
الإجابة 5
#!/bin/bash
# report on one path given on the command line
if [ $# -eq 0 ]
then
echo "Usage: $0 path"
elif [ ! -e $1 ]
then
echo "$1 does not exist"
elif [ -d $1 ]
then
echo "$1 is a directory"
elif [ -x $1 ]
then
echo "$1 is an executable file"
else
echo "$1 is a file, not executable"
fi$ ./filecheck.sh
Usage: ./filecheck.sh path
$ ./filecheck.sh nofile.txt
nofile.txt does not exist
$ ./filecheck.sh /etc
/etc is a directory
$ ./filecheck.sh bill.sh
bill.sh is an executable file
$ ./filecheck.sh salesdata.txt
salesdata.txt is a file, not executableترتيب الاختبارات مهم: يتحقق السكربت أولا من إعطاء وسيط ($#)، ثم من وجود المسار (فالمعامل ! يحول "موجود" إلى "غير موجود")، وعندئذ فقط يسأل عن نوع المسار. وقد جعل bill.sh قابلا للتنفيذ بالأمر chmod 755؛ أما salesdata.txt فلا.