كتابة سكربتات Bash - الجزء الثاني
بنهاية هذا الدرس ستكون قادرا على كتابة سكربتات Bash تكرر العمل باستخدام الـ loops، وتنظم المنطق في functions، وتستدعي سكربتا من سكربت آخر، وتبني menus تفاعلية، وتتعامل مع الـ strings، وتقرأ أو تكتب الملفات من خلال الـ redirection والـ pipes.
الأهداف
- تكرار العمل باستخدام
forعلى قائمة كلمات، وwhileمع عداد، وuntil؛ وتحديث العدادات بـ(( )). - كتابة الـ functions بصيغتي التعريف، وتمرير الـ arguments، والحفاظ على الـ variables كـ
local، وإرجاع نتيجة من خلال global variable. - تشغيل سكربت من سكربت آخر باستخدام
sourceوbashوevalوexec، ومعرفة الفرق بين كل واحد منها. - بناء menu بـ
select، ومعالجة الـ arguments الآتية من سطر الأوامر بـcase، والتعامل مع الـ strings باستخدامIFSوread -aو${var^^}وtr. - قراءة ملف سطرا بسطر، والكتابة بـ
>و>>، وربطsortوgrepوwcبالـ pipes.
المتطلبات السابقة
- الدرس السابق عن كتابة سكربتات Bash: سطر الـ shebang، جعل السكربت قابلا للتنفيذ، الـ variables،
read،$1/$2،$?،exit، حساب$(( ))،bc، وif/elif/elseمع[[ ]]. - طرفية Linux (تثبيت أصلي، أو جهاز افتراضي، أو WSL).
- حاسبة
bc، مطلوبة لمثال واحد على الـ functions. إذا لم تكن مثبتة، ثبتها بمدير الحزم الخاص بتوزيعتك. - مجلد عمل واحد لكل سكربتات هذا الدرس، لأن سكربت مهمة يستدعي سكربتات مساعدة لمهمة أخرى بالاسم.
معنى "أنشئ السكربت" فيما يلي: افتح السطر في محرر نصوص، والصقه، واحفظه بامتداد .sh، ثم شغله بـ bash name.sh (أو اجعله قابلا للتنفيذ مرة واحدة وشغله بعدها بـ ./name.sh). خطوتان قصيرتان في هذا الدرس تكرران عمدا سكربتات من الدرس السابق: هي مراجعة، وضعت حيث يبني هذا الدرس عليها.
المفهوم الأول: الـ Loops
التعريف. الـ loop يكرر مجموعة من الأوامر. عند Bash ثلاث كلمات مفتاحية للـ loops، for و while و until، وجسم كل loop يقع بين do و done.
الغرض. أي عمل يجب أن يحدث مرة واحدة لكل عنصر (كل يوم من أيام الأسبوع، كل كلمة من جملة، كل رقم من 1 إلى 20) يكتب مرة واحدة داخل الجسم ويشغله الـ loop بعدد المرات اللازم.
كيف يعمل. for يأخذ الكلمة التالية من قائمة في كل مرة يمر بها ويتوقف عندما تنتهي القائمة. while و until يختبران شرطا قبل كل مرة:
flowchart TD
A[Start] --> B{Condition}
B -- "while: true / until: false" --> C[Run the body between do and done]
C --> D["Update the counter, for example counter++"]
D --> B
B -- "while: false / until: true" --> E[Continue after done]الفرق الوحيد بين while و until: while يعمل ما دام الاختبار ينجح، until يعمل ما دام الاختبار يفشل.
for على قائمة كلمات
الصيغة الأكثر شيوعا تمر على قائمة كلمات، كلمة واحدة في كل مرة:
#!/bin/bash
# Read a weekday name in each iteration of the loop
for day in Monday Tuesday Wednesday Thursday Friday Saturday Sunday
do
if [[ $day == 'Monday' || $day == 'Thursday' ]]
then
echo "Meeting on $day at 9:30 am"
elif [[ $day == 'Tuesday' || $day == 'Wednesday' || $day == 'Friday' ]]
then
echo "Training on $day at 11:00 am"
else
echo "$day is Holiday"
fi
donefor day in ...:dayهو الـ loop variable. في كل مرة يأخذ الكلمة التالية من القائمة بعدin.do/done: الجسم. كل ما بينهما يعمل مرة واحدة لكل كلمة.- الـ
if/elif/elseداخل الجسم تقارن$dayالحالية بقيم نصية، فيحصل كل يوم على رسالته الخاصة.
for على كلمات جملة
القائمة بعد in ليست بالضرورة مجموعة أسماء منفصلة. جملة تكتب هناك تنقسم عند الفراغات، وتصبح كل كلمة مرة واحدة من الـ loop، وهذه هي الطريقة التي يقرأ بها الـ for loop بيانات نصية كلمة بكلمة:
#!/bin/bash
# Read each word of a text by using for loop
for value in Bash Programming for the Beginners
do
echo $value
donefor value in Bash Programming for the Beginners: الكلمات الخمس تشكل القائمة؛valueتستقبلها واحدة تلو الأخرى.echo $value: يطبع الكلمة الحالية في سطرها الخاص، فيكون الناتجBashوProgrammingوforوtheوBeginners، كلمة في كل سطر.
while مع عداد
#!/bin/bash
echo "Print the numbers which are even and divisible by 5"
counter=1
while [ $counter -le 20 ]
do
if [[ $counter%2 -eq 0 && $counter%5 -eq 0 ]]
then
echo "$counter"
fi
((counter++))
done
echo "Done"counter=1: الـ loop variable يجب أن يضبط قبل الـ loop، وإلا يفشل الاختبار فورا.while [ $counter -le 20 ]: الجسم يعمل ما دامcounterأصغر من أو يساوي 20.[[ $counter%2 -eq 0 && $counter%5 -eq 0 ]]:-eqداخل[[ ]]يقيم الطرفين حسابيا، فـ$counter%2تصبح، مثلا،10%2، وهي 0.%هو عامل باقي القسمة (remainder).((counter++)): يضيف 1 إلىcounter. نسيان هذا السطر يعطي loop لا نهائي؛ اضغطCtrl+Cإذا حدث ذلك.
until
#!/bin/bash
n=20
until [ $n -lt 0 ]
do
if [[ $n%2 -gt 0 ]]
then
echo $n
fi
((n=$n-1))
doneuntil [ $n -lt 0 ]: الجسم يعمل حتى تصبحnأصغر من 0، بعبارة أخرى ما دامتnتساوي 0 أو أكبر.[[ $n%2 -gt 0 ]]: باقي قسمة أكبر من 0 يعني أنnفردية، فالسكربت يطبع الأرقام الفردية عدا تنازليا من 19 إلى 1.((n=$n-1)): العداد ينزل بدلا من أن يصعد. هذه نفس خطوة التنقيص المسبق((--N))التي ظهرت سابقا.
المفهوم الثاني: الـ Functions
التعريف. الـ function هو مجموعة أوامر مسماة تعرفها مرة واحدة وتستدعيها بقدر ما تحتاج. استدعاؤه هو نفس كتابة اسمه كأمر.
الغرض. عندما تحتاج المجموعة نفسها في أكثر من مكان في السكربت، يحتفظ الـ function بها مرة واحدة، وكل مكان يستدعي الاسم بدلا من تكرار السطور.
كيف يعمل. التعريف يعطي الاسم، وأقواسا فارغة، والجسم بين أقواس معقوفة. الاستدعاء هو الاسم فقط، وقد يتبعه قيم؛ داخل الـ function تصبح هذه القيم $1 و $2، تماما كما هي الـ arguments التي يستقبلها سكربت من سطر الأوامر. تعود النتيجة إما كنص يطبعه الـ function أو كـ variable يضبطه الـ function ويقرؤه المستدعي بعد ذلك:
flowchart LR
A[Main script] -- "call: calculate_area 5, the 5 becomes $1" --> B[Function body]
B -- "sets a global variable, for example return_str" --> A
B -- "prints a line with echo" --> T[Terminal]صيغ التعريف، والـ Arguments، و local
#!/bin/bash
function print_message()
{
echo "Bash programming for beginner"
}
function ret_strdata()
{
return_str="Learn bash programming step by step"
}
function calculate_area()
{
local radius=$1
area=$(echo $radius*$radius*3.14 | bc)
echo "Area of the circle is $area"
}
print_message
ret_strdata
echo $return_str
echo "Enter the radius value"
read rad
calculate_area $radfunction print_message() { ... }: صيغة التعريف الأولى. الكلمة المفتاحيةfunctionاختيارية، فـprint_message() { ... }هي الصيغة الثانية وتعني الشيء نفسه تماما. كلتاهما تحتاج الأقواس الفارغة والأقواس المعقوفة.ret_strdataتضبطreturn_strبدونlocal. الـ variables تكون global بشكل افتراضي، فيستطيع السكربت الرئيسي طباعتها بعد الاستدعاء: أسلوب "الإرجاع عبر global variable"، وهذه هي الطريقة التي يعيد بها function نصا في Bash.calculate_areaيستقبل الـ argument الخاص به بالطريقة التي يستقبلها سكربت: القيمة الأولى بعد اسم الـ function تصبح$1.local radius=$1تنسخها إلى variable موجود فقط داخل الـ function؛ بدونlocal، function يضبط اسما يستخدمه السكربت الرئيسي يكتب فوقه.$(echo $radius*$radius*3.14 | bc):(( ))تتعامل مع الأعداد الصحيحة فقط، فالضرب العشري يمرر بـ pipe إلىbc، و$( )يلتقط ما يطبعهbc. النجمة*المفردة قد يوسعها الـ shell إلى أسماء ملفات؛ هنا النمط5*5*3.14لا يطابق أي ملف فيصل إلىbcبلا تغيير، إلا أن وضع التعبير بين علامتي اقتباس كـ"$radius * $radius * 3.14"هو العادة الأسلم.print_message،ret_strdata،calculate_area $rad: الاستدعاءات. التعريفات يجب أن تسبق الاستدعاءات؛ Bash يقرأ من الأعلى إلى الأسفل ولا يعرف function قبل أن يمر بتعريفه.
الـ Scope: global بشكل افتراضي، و local عند الطلب
تستحق الكلمة المفتاحية local من السطر السابق عرضا خاصا بها، لأن الفرق لا يظهر إلا عندما يستخدم function والسكربت الرئيسي الاسم نفسه:
#!/bin/bash
counter=100
show_local()
{
local counter=5
echo "Inside function counter = $counter"
}
show_local
echo "Outside function counter = $counter"counter=100: global variable يضبطه السكربت الرئيسي.local counter=5: داخل الـ function،counterثانية ومنفصلة موجودة فقط حتى ينتهي الـ function.show_localثمecho "Outside ...": الـ function يطبع5؛ السكربت الرئيسي يطبع100رغم ذلك، لأن الـ global variable لم يلمس أبدا. أزل كلمةlocalوكلا السطرين يطبعان5.
بين سكربتين كاملين، تنتقل النتيجة كـ exit code بدلا من ذلك: السكربت ينتهي بـ exit 0 أو exit 1، والسكربت الذي شغله يفحص $?.
المفهوم الثالث: استدعاء سكربت من سكربت آخر
التعريف. يمكن لسكربت أن يبدأ سكربتا آخر بالطريقة نفسها التي تبدأه بها من سطر الأوامر. أربعة أوامر تفعل ذلك، وتختلف في أي shell يشغل السكربت الثاني وماذا يحدث للسكربت الأول.
الغرض. تقسيم العمل إلى عدة ملفات يبقي كل واحد قصيرا، والمستدعي هو من يقرر إن كانت الـ variables الخاصة بالملف الثاني تبقى، وإن كانت الـ arguments تمرر، وإن كان المستدعي يستمر بعد ذلك.
كيف يعمل. الـ child shell هو عملية bash ثانية يبدأها الأول؛ يحصل على نسخته الخاصة من الـ variables، وكل ما يضبطه يختفي عند خروجه. source فقط يتجنب الـ child shell:
flowchart TD
S[callpro.sh in shell A] --> S1["source add.sh: same shell, variables stay"]
S --> S2["bash subtract.sh 50 20: child shell B, returns to A"]
S --> S3["eval bash multiply.sh $a $b: build the command text, then run it"]
S --> S4["exec full/path/divide.sh 30: shell A replaced, nothing after runs"]source file: يقرأ أوامر الملف داخل الـ shell الحالي؛ أي variable يضبطه الملف يبقى موجودا بعد ذلك.sourceتستقبل الاسم كـ file argument، فـadd.shبلا مسار في المجلد الحالي يعمل.bash file args: يبدأ child shell. الـ arguments تصبح$1و$2داخله؛ الـ variables الخاصة به تختفي عند انتهائه. مثلsource، يستقبلbashالاسم كـ file argument، فـsubtract.shبلا مسار يعمل.eval bash file $a $b:evalيجمع أولا الـ arguments الخاصة به في سطر نص واحد، ثم يشغل ذلك النص كأمر. هذا مهم عندما يبنى نص الأمر فقط وقت التشغيل، مثلا عندما يعيش الأمر كله داخل variable. مع variables عادية، كما في الأمثلة هنا، تكون النتيجة مطابقة تماما لكتابةbash multiply.sh 6 7؛ السطر يعرضevalفقط لأجل صيغته.exec file args: يستبدل السكربت الجاري تشغيله بالبرنامج المستهدف، فالسطور بعدexecلا تنفذ أبدا.execيبحث عن هدفه فيPATHتماما كأمر مكتوب، فـdivide.shبلا مسار يفشل بالرسالةexec: divide.sh: not found؛ مسار كامل أو صيغة./divide.shكلاهما يعمل.$PWDيحمل نفس النص الذي يطبعه أمرpwd: المسار الكامل للمجلد الحالي.
المفهوم الرابع: الـ Menus بـ select والـ Arguments بـ case
التعريف. select name in list يطبع القائمة كـ menu مرقم، ويظهر السطر التوجيهي #?، ويقرأ رقما، ويخزن الكلمة المطابقة في name. case يقارن قيمة واحدة بعدة أنماط ويشغل فرع أول مطابقة.
الغرض. select يعطي السكربت menu تفاعلي في سطر واحد. case يستبدل سلسلة طويلة من if/elif عندما يجب مطابقة قيمة واحدة مع عدة كلمات ثابتة.
كيف يعمل. select هو loop: يتكرر حتى يخرج السكربت، فالجسم يختبر خيار "Exit" وينادي exit 0:
flowchart TD
A[Print the numbered list] --> B["Show the #? prompt"]
B --> C[Read a number]
C --> D[Store the matching word in language]
D --> E{Is it Exit?}
E -- yes --> F[exit 0]
E -- no --> G[echo the selected word]
G --> Bمع cut، يتعامل case مع الـ arguments بصيغة key=value:
for arg in "$@"
do
key=$(echo $arg | cut -f1 -d=)
value=$(echo $arg | cut -f2 -d=)
case $key in
name) echo "Student's name = $value";;
mark) echo "Obtained mark = $value";;
*)
esac
done"$@": كل الـ arguments من سطر الأوامر، كل واحدة تبقى كلمة واحدة حتى لو احتوت فراغات (فـname="Ahmed Ali"تصل كاملة).cut -f1 -d=: قطع النص عند=(-d=تضبط الفاصل) والاحتفاظ بالحقل 1؛-f2تحتفظ بالحقل 2.case $key in ... esac: كل نمط ينتهي بـ)، وكل فرع ينتهي بـ;;، و*)هو الافتراضي الذي يطابق أي شيء. الفرع الافتراضي هنا فارغ، فمفتاح غير معروف يتجاهل بصمت.
المفهوم الخامس: الـ Strings
التعريف. الـ string هو قطعة نص محفوظة في variable. لا توجد عند Bash string functions منفصلة؛ العمليات الشائعة تتم بالاقتباس (quoting)، والتوسيعات (expansions)، وأوامر صغيرة.
الغرض. السكربتات تدمج النصوص، وتقارنها، وتقسمها إلى كلمات، وتغير حالة أحرفها باستمرار: بناء رسالة، فحص إجابة، قراءة سطر إدخال كلمة بكلمة.
كيف يعمل.
- الدمج (concatenation): وضع الـ variables جنبا إلى جنب،
echo "$string1$string2". - المقارنة:
if [ "$text" == "Python" ]. ضع الـ variable بين علامتي اقتباس حتى لا يكسر إدخال فارغ الاختبار. - التقسيم وتغيير حالة الأحرف هما العمليتان الجديدتان، تعرض كل منهما بسطر أدناه.
تقسيم string إلى array
الـ array هو variable واحد يحمل عدة قيم، مرقمة من 0: arr[0] هي العنصر الأول، و "${arr[@]}" تسرد كل عنصر كل كلمة منفصلة. read -a arr يخزن كل كلمة من إدخاله كالعنصر التالي في arr.
#!/bin/bash
echo "Enter a string value"
read text
IFS=' '
read -a arr <<< "$text"
for value in "${arr[@]}";
do
printf "$value\n"
doneread text: السطر المكتوب كاملا يصل إلىtext.IFS=' ': يضبط الـ Internal Field Separator، الحرف الذي يقسم عليهread، إلى فراغ.read -a arr <<< "$text":<<<يغذي الـ string إلىreadكما لو كتب، و-aيخزن كل كلمة كالعنصر التالي فيarr.for value in "${arr[@]}";: الـ loop يمر على العناصر، عنصرا في كل مرة. الفاصلة المنقوطة;النهائية قبل نهاية السطر اختيارية وغير ضارة.printf "$value\n":printfيطبع الـ format string كما هو بالضبط، فيجب كتابة\nصراحة للحصول على سطر جديد؛echoيضيف سطرا جديدا من تلقاء نفسه. كتابةLearn Bash programmingتطبع الكلمات الثلاث في ثلاثة سطور.
الـ loop الذي قسم الجملة في المفهوم الأول قسم الكلمات بالطريقة نفسها، لكن كلماتها كانت ثابتة في السكربت؛ هذا السطر يقسم نصا يكتب وقت التشغيل.
تغيير حالة الأحرف
#!/bin/bash
text1='[email protected]'
echo "${text1^^}"
text2='Bash Programming Basics'
echo $text2 | tr '[:upper:]' '[:lower:]'${text1^^}: توسيع Bash الخاص بالأحرف الكبيرة (uppercase)؛ يطبع[email protected].echo $text2 | tr '[:upper:]' '[:lower:]':trيحول كل حرف كبير يمر عبر الـ pipe إلى حرف صغير، فيعطيbash programming basics. ضع تعبيرات الأقواس المربعة بين علامتي اقتباس حتى لا يعاملها الـ shell كأنماط أسماء ملفات.
المفهوم السادس: الملفات والـ Pipes
التعريف. المخرجات القياسية (standard output) هي ما يطبعه أمر على الطرفية؛ المدخلات القياسية (standard input) هي ما يقرؤه من لوحة المفاتيح. كلاهما يمكن إعادة توجيهه: > و | ينقلان المخرجات إلى مكان آخر، و read و | يغذيان المدخلات من مكان آخر.
الغرض. الـ redirection تتيح للسكربت الاحتفاظ بالنتائج في ملف وقراءتها بعد ذلك؛ الـ pipe يتيح لعدة أوامر أن تعمل كواحد بدون ملف مؤقت.
كيف يعمل. قراءة ملف:
#!/bin/bash
echo "Reading file using cat command"
content=`cat hardware.txt`
echo $content
echo "Reading file line by line using loop"
filename='hardware.txt'
while read line;
do
echo $line
done<$filenamecontent=`cat hardware.txt`: علامات الاقتباس الخلفية (backticks) هي الصيغة القديمة لـ$( )؛ الملف كاملا يصل إلى variable واحد.echo $contentبدون علامات اقتباس يطبعه في سطر واحد، لأن الأسطر الجديدة تصبح فراغات.while read line;:read lineتنجح ما دام هناك سطر متبق، فتتوقفwhileعند نهاية الملف. الفاصلة المنقوطة النهائية قبل نهاية السطر اختيارية، تماما كما في الـforloop في المفهوم الخامس.done<$filename: يعيد توجيه الملف إلى الـ loop الكامل، فكلreadيأخذ السطر التالي من الملف بدلا من لوحة المفاتيح، ويطبع كل سطر منفصلا.
الكتابة: echo $str1 > test.txt يخلق الملف أو يكتب فوقه؛ echo $str2 >> test.txt يضيف إليه.
الـ pipe (|) ينقل المخرجات القياسية لأمر إلى المدخلات القياسية للأمر التالي:
flowchart LR
A[marks.txt] --> B[sort] -- sorted lines --> C["grep 'Keya'"] -- matching lines --> D[wc -l] --> E[2]المهمة الأولى: أربع حلقات في سكربت واحد
- أنشئ
loops.shبالحلقات الأربع من المفهوم الأول مجمعة في ملف واحد:
#!/bin/bash
# Read a weekday name in each iteration of the loop
for day in Monday Tuesday Wednesday Thursday Friday Saturday Sunday
do
if [[ $day == 'Monday' || $day == 'Thursday' ]]
then
echo "Meeting on $day at 9:30 am"
elif [[ $day == 'Tuesday' || $day == 'Wednesday' || $day == 'Friday' ]]
then
echo "Training on $day at 11:00 am"
else
echo "$day is Holiday"
fi
done
# Read each word of a text by using for loop
for value in Bash Programming for the Beginners
do
echo $value
done
echo "Print the numbers which are even and divisible by 5"
counter=1
while [ $counter -le 20 ]
do
if [[ $counter%2 -eq 0 && $counter%5 -eq 0 ]]
then
echo "$counter"
fi
((counter++))
done
echo "Done"
n=20
until [ $n -lt 0 ]
do
if [[ $n%2 -gt 0 ]]
then
echo $n
fi
((n=$n-1))
done- شغل
bash loops.sh. المتوقع:
Meeting on Monday at 9:30 am
Training on Tuesday at 11:00 am
Training on Wednesday at 11:00 am
Meeting on Thursday at 9:30 am
Training on Friday at 11:00 am
Saturday is Holiday
Sunday is Holiday
Bash
Programming
for
the
Beginners
Print the numbers which are even and divisible by 5
10
20
Done
19
17
15
13
11
9
7
5
3
1- غير
until [ $n -lt 0 ]إلىuntil [ $n -lt 10 ]وشغل مرة أخرى. المتوقع: الكتلة الأخيرة تتوقف الآن عند11، لأن الـ loop ينتهي بمجرد أن تنزلnتحت 10. - أزل سطر
((counter++))من كتلةwhile، شغل السكربت، واضغطCtrl+Cبعد ثانية. المتوقع: "Done" لا يطبع أبدا، لأنcounterتبقى عند 1 للأبد. أعد السطر كما كان.
المهمة الثانية: الـ Functions، الـ Local Variables، والـ Exit Codes
- أنشئ
func.shمن الدوال الثلاث في المفهوم الثاني وشغلbash func.sh، مكتبا5عند السطر التوجيهي. المتوقع:
Bash programming for beginner
Learn bash programming step by step
Enter the radius value
5
Area of the circle is 78.50- أنشئ
scope.shمن سطرshow_localفي المفهوم الثاني وشغلbash scope.sh. المتوقع:
Inside function counter = 5
Outside function counter = 100- احذف الكلمة
localفيshow_localوشغل مرة أخرى. المتوقع: يعرض السطران الآن5، لأن الـ function كتب فوق global variable خاص بالسكربت. - مراجعة من الدرس السابق: يسلم سكربت نتيجة إلى من استدعاه عبر exit code. أنشئ الملفين أدناه، ثم شغل
bash second.shمرتين، مكتبا55ثم110.
first.sh:
#!/bin/bash
echo "Enter a numeric value"
read n
if [[ $n -le 100 ]]
then
exit 0
else
exit 1
fisecond.sh:
#!/bin/bash
bash "first.sh"
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المتوقع للتشغيلين:
Enter a numeric value
55
The input number is less than or equal to 100
Enter a numeric value
110
The input number is greater than 100second.sh لا يرى العدد أبدا، بل فقط exit code الخاص بـ first.sh.
المهمة الثالثة: استدعاء السكربتات بـ source و bash و eval و exec
- أنشئ السكربتات المساعدة الأربعة. كل سطر ملف كامل.
add.sh:
#!/bin/bash
a=60
b=40
((result=$a+$b))
echo "The addition of $a+$b=$result"subtract.sh:
#!/bin/bash
a=$1
b=$2
((result=$a-$b))
echo "The subtraction of $a-$b=$result"multiply.sh:
#!/bin/bash
((result=$1*$2))
echo "The multiplication of $1 and $2 is $result"divide.sh:
#!/bin/bash
a=$1
b=2
((result=$a/$b))
echo "The division of $a by $b is $result"- اجعل السكربتات الأربعة قابلة للتنفيذ، لأن
execيحتاج هدفه قابلا للتنفيذ. - أنشئ
callpro.sh.execيبحث عن هدفه كأمر مكتوب، فيحتاج المسار الكامل لـdivide.sh؛$PWDيوفر المسار الذي يطبعهpwd، فيعمل السكربت بغض النظر عن المجلد الذي أنشئ فيه:
#!/bin/bash
script1="add.sh"
script2="subtract.sh"
script3="multiply.sh"
script4="$PWD/divide.sh"
source "$script1"
echo "After source, result is still visible here: $result"
bash $script2 50 20
echo "Enter the value of a"
read a
echo "Enter the value of b"
read b
eval bash $script3 $a $b
exec $script4 30
echo "This line never runs because exec replaced the shell"- شغل
bash callpro.shوأدخل6ثم7. المتوقع:
The addition of 60+40=100
After source, result is still visible here: 100
The subtraction of 50-20=30
Enter the value of a
6
Enter the value of b
7
The multiplication of 6 and 7 is 42
The division of 30 by 2 is 15السطر الثاني يعرض 100 لأن source شغل add.sh في الـ shell نفسه. سطر eval يطبع نفس ما تطبعه bash multiply.sh 6 7، كما يوضح المفهوم الثالث. سطر echo الأخير لا يظهر أبدا لأن exec استبدل ذلك الـ shell بـ divide.sh.
- غير
source "$script1"إلىbash "$script1"وشغل مرة أخرى. المتوقع: السطر الثاني ينتهي مباشرة بعد النقطتين، لأنadd.shعمل في child shell (عمليةbashثانية) وضاعresultالخاص به عند خروجه. - غير
script4="$PWD/divide.sh"إلىscript4="divide.sh"وشغل مرة أخرى. السطر الأخير المتوقع:callpro.sh: line 18: exec: divide.sh: not found، لأنexecبحث فيPATHوالمجلد الحالي ليس فيه. أعد صيغة$PWD.
المهمة الرابعة: menu وحاسبة سطر أوامر
- أنشئ
menu.sh:
#!/bin/bash
echo "Select your favorite language"
select language in C# Java PHP Python Bash Exit
do
if [[ $language == "Exit" ]]
then
exit 0
else
echo "Selected language is $language"
fi
doneselect language in C# Java PHP Python Bash Exit: الكلمات الست هي الـ menu؛ الكلمة المختارة تصل إلىlanguage.if [[ $language == "Exit" ]]ثمexit 0: الطريقة الوحيدة للخروج من الـ loop.echo "Selected language is $language": أي اختيار آخر يطبع ويعود السطر التوجيهي#?.
- شغل
bash menu.sh، اكتب3، ثم6. المتوقع:
Select your favorite language
1) C#
2) Java
3) PHP
4) Python
5) Bash
6) Exit
#? 3
Selected language is PHP
#? 6الـ menu يطبع مرة واحدة، والسطر التوجيهي #? يعود بعد كل اختيار، و 6 ينهي السكربت من خلال exit 0.
- مراجعة: أنشئ
cl1.sh، الذي يقرأ ثلاثة وسائط: عدد، و operator، وعدد.xتمثل الضرب لأن*مفردة في سطر الأوامر قد يوسعها الـ shell إلى أسماء ملفات.
#!/bin/bash
echo "Argument values are: $1 $2 $3"
operand1=$1
operand2=$3
operator=$2
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))
fi
echo -e "Result is = $result"echo -e يفعل رموز الهروب (backslash escapes) مثل \n داخل النص؛ هنا لا يوجد أي منها في النص، فلا يظهر فرق مرئي و echo العادي كان سيطبع السطر نفسه.
- شغله أربع مرات:
bash cl1.sh 6 + 3،bash cl1.sh 6 - 3،bash cl1.sh 6 x 3،bash cl1.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- أنشئ
cl2.shمن سطرcaseفي المفهوم الرابع، مع#!/bin/bashوecho "Argument values are: $1 $2"كأول سطرين له. شغلbash cl2.sh name="Ahmed Ali" mark=90. المتوقع:
Argument values are: name=Ahmed Ali mark=90
Student's name = Ahmed Ali
Obtained mark = 90- عدل الفرع الافتراضي الفارغ: استبدل السطر
*)بـ*) echo "Unknown argument: $key";;. شغلbash cl2.sh name="Ahmed Ali" mark=90 age=20. المتوقع:
Argument values are: name=Ahmed Ali mark=90
Student's name = Ahmed Ali
Obtained mark = 90
Unknown argument: ageالسطر الأول لا يزال يعرض وسيطين فقط لأنه يطبع $1 $2؛ الـ loop على "$@" يرى الثلاثة، و age يصل إلى الفرع *).
المهمة الخامسة: الـ Strings والملفات
- أنشئ
split.shمن سطر التقسيم في المفهوم الخامس. شغله واكتبLearn Bash programming. المتوقع:
Enter a string value
Learn Bash programming
Learn
Bash
programmingالإدخال يحتوي ثلاث كلمات، فالـ array يحتوي ثلاثة عناصر والـ loop يطبع ثلاثة سطور.
- أنشئ
case.shمن سطر تغيير حالة الأحرف في المفهوم الخامس وشغلbash case.sh. المتوقع:
[email protected]
bash programming basics- أنشئ
hardware.txtبكلمة واحدة في كل سطر:Monitor،Keyboard،Mouse،Scanner،Printer. أنشئreadfile.shمن سطر المفهوم السادس وشغلbash readfile.sh. المتوقع:
Reading file using cat command
Monitor Keyboard Mouse Scanner Printer
Reading file line by line using loop
Monitor
Keyboard
Mouse
Scanner
Printer- أنشئ
writefile.sh، شغله، واكتبHello BashثمSecond line:
#!/bin/bash
echo "Enter some text"
read str1
echo $str1 > test.txt
echo "Enter some other text"
read str2
echo $str2 >> test.txt
echo `cat test.txt`read str1ثمecho $str1 > test.txt: النص الأول يخلق الملف (أو يكتب فوقه).read str2ثمecho $str2 >> test.txt: النص الثاني يضاف كسطر جديد.echo `cat test.txt`: يطبع محتوى الملف؛ علامات الاقتباس الخلفية بدون اقتباس تحول السطرين إلى سطر واحد، كما في المفهوم السادس.
الجلسة المتوقعة:
Enter some text
Hello Bash
Enter some other text
Second line
Hello Bash Second lineثم شغل cat test.txt وتوقع السطرين في صفين منفصلين: > خلق الملف و >> أضاف إليه. شغل السكربت مرة أخرى بكلمات مختلفة: الملف يحتفظ فقط بالزوج الجديد، لأن > كتب فوقه.
المهمة السادسة: الـ Pipes
- أنشئ
marks.txtبهذه الأسطر الستة تماما (الأعمدة مفصولة بفراغات أو tabs):
Asraf CSE-409 79
Kabir CSE-304 95
Keya CSE-101 67
Asraf CSE-304 88
Keya CSE-409 90
Asraf CSE-101 92- اعمل المهمة بالطريقة الطويلة، ثلاثة أوامر وملف مؤقت:
sort marks.txt
grep 'Keya' marks.txt > temp.txt
wc -l temp.txtالمتوقع لناتج الأوامر الثلاثة:
Asraf CSE-101 92
Asraf CSE-304 88
Asraf CSE-409 79
Kabir CSE-304 95
Keya CSE-101 67
Keya CSE-409 90
2 temp.txtsort يرتب السطور أبجديا (السطور الثلاثة لـ Asraf حسب رمز المقرر)، و grep يكتب سطري Keya في temp.txt، و wc -l يعدهما.
- اعملها بالطريقة القصيرة:
sort marks.txt | grep 'Keya' | wc -l. المتوقع:2. لا حاجة إلىtemp.txt، فكل مخرج ينتقل مباشرة إلى الأمر التالي. - غير الـ pipeline ليعد
Asrafبدلا من ذلك. المتوقع:3.
الخلاصة
for word in listيمر على قائمة، سواء كانت القائمة مجموعة أسماء أو كلمات جملة؛whileيعمل ما دام الاختبار ينجح، وuntilما دام يفشل. كل جسم يقع بينdo ... done.- حدث العدادات بـ
((counter++))أو((n=$n-1))؛ loop بدون تحديث لا ينتهي أبدا. - الـ functions تعرف بصيغة
name() { }مع أو بدون الكلمة المفتاحيةfunction، ويجب أن تعرف قبل استدعائها، وتستقبل الـ arguments كـ$1و$2. الـ variables تكون global بشكل افتراضي؛localتحفظ واحدا داخل الـ function فقط. الـ function يسلم نصا عبر ضبط global variable؛ السكربتات الكاملة تسلم نتيجة بـexit N، تقرأ عبر$?. sourceيحفظ الـ variables الخاصة بالسكربت المستدعى؛bashيشغله في child shell؛evalيبني نص الأمر أولا ويتصرف كـbashعندما يكون النص عاديا؛execيستبدل الـ shell الحالي نهائيا ويحتاج مسارا كاملا مثل$PWD/divide.sh.selectيبني menu مرقم بالسطر التوجيهي#?ويتكرر حتىexit؛case ... esacيطابق الأنماط، و*)هو الافتراضي؛cut -f1 -d=يقسمkey=value.- الـ Strings: الدمج بالتجاور، المقارنة بـ
[ "$a" == "$b" ]، التقسيم إلى array بـIFSمعread -aوالمرور عليه بـ"${arr[@]}"، تغيير حالة الأحرف بـ${var^^}أوtr.printfيحتاج\nصريحة. - اقرأ الملفات بـ
while read line; do ...; done<file، اكتب بـ>(الكتابة فوق الملف) و>>(الإضافة)، وسلسل الأوامر بـ|، الذي يربط المخرجات القياسية لأمر بالمدخلات القياسية للأمر التالي.