6.2 لغة البرمجة bash كتاب لينكس الشامل >>

6.2 لغة البرمجة bash

الفهرس

6.2.1 لماذا bash

تحدثنا من قبل عن أصل كلمة bash وهي تعني Bourne Again SHell وهو نفسه مفسر الأوامر التي تطبعها والتي تحدثنا عنها من قبل في قسم سطر الأوامر ليس مخيفا وهو أيضا يمكنه أن يحاكي مفسر sh التقليدي (القديم) وذلك بعمل وصلة link منه باسم sh ، ولكن bash ليس أفضل مفسر للأوامر ولكنه الأكثر شهرة وهناك الكثير غيره مثل ksh و csh و zsh وتحدثنا في قسم نظرة تشريحية في لينكس كيف تختار مفسر الأوامر الذي تريد وذلك بتعديل آخر حقل من السطر المقابل للمستخدم الذي تريد من ملف /etc/passwd ولأن معظم التوزيعات تختار bash بشكل تلقائي أحببت أن أتحدث عنه

تكمن أهمية تعلم هذه الطريقة في أنك تتعلم مقدمة عن لغات البرمجة التفسيرية وكيفية التعامل معها في لينكس،وأيضا في القيام بالواجبات الموكولة إليك كمدير للنظام والتي تقوم بها بشكل متكرر ولتوفر على نفسك طباعة عدد كبير من الأوامر وتعتبر اللغات التفسيرية طريقة سريعة وقذرة ؛ سريعة ذلك أن عمل برنامج عليها لايحتاج سوى لكتابة ذلك البرنامج في أي محرر نصوص كما هو من دون تصنيف compile وقذرة لأن الأخطاء التي قد تحدث أثناء تنفيذ البرنامج run-time متوقعة وقد تحدث دون أن يعطيك تحذير عليها (لأنك لم تعمل compile) وأيضا لأن البرنامج قد ينفذ برامج خارجية قد تختلف اصدارتها عن الإصدار الذي صمم من أجله ولأنها ليست بلغة الآلة تكون أقل سرعة (هذا لايتناقض مع كون كتابة البرنامج لا تأخذ وقت)

6.2.2 البرامج التفسيرية scripts

ذكرنا سابقاً أنه عند تنفيذ ملف فإن لينكس يعرف كيف سينفذ هذا الملف من بنيته الداخلية وليس من الإمتداد،نفصل ذلك الآن فنقول إذا كان الملف يبدأ ب #! والتي تسمى sha-bang فإن هذا الملف برنامج بلغة تفسيرية وما يأتي بعدها هو اسم البرنامج المفسر مثلا ملف يحتوي على التالي

#!/bin/bash
# filename.sh : this is a do-nothing script
...
# bash code goes here
...
عند تشغيله ستم تنفيذ برنامج /bin/bash وتمرير هذا الملف ليفسره وسيكون له نفس التأثير لو كتبنا
bash$ /bin/bash filename.sh
وهذا مثال آخر
#!/usr/bin/perl -w
# filename.pl : this is a perl script
...
# perl code goes here
...
سيكون له نفس التأثير لو كتبنا
bash$ /usr/bin/perl -w filename.pl

tipتلميح

عند النقر المزدوج على ملفات ذات الامتداد الخاص بلغة تفسيرية مثل hello.shفي غنوم و KDE فإنه قد يفتحه في محرر نصوص بدلاً من تنفيذه. إذا لم تحب هذا السلوك اجعله بدون امتداد مثلاً hello-sh

فإذا كنت لا تعلم مكان البرنامج المفسر استخدم امر which ليطبع لك المسار لذلك البرنامج مثلاً
bash$ which bash
/bin/bash
أما إذا كنت تريد أن يعمل عند مستخدم لديه البرنامج المفسر في مكان يختلف عن الذي لديك استعمل برنامج env متبوعا باسم المفسر هكذا
#!/usr/bin/env bash
# filename.sh : this is a do-nothing script
...
# bash code goes here
...

باختصار الخطوة الأول هي أن تفتح محرر النصوص المفضل لديك ثم تكتب #!/bin/bash ثم تكتب البرنامج ثم تخزنه بأي اسم تريد وأي امتداد تريد ولكن يفضل أن يكون ذا معنى مثل .sh

الخطوة الثانية هي أن تسمح للجميع بتنفيذ هذا الملف وذلك بكتابة chmod +x filename.sh الآن لتنفيذ هذا البرنامج كل ما عليك هو كتابة اسمه والمسار مثلا ~/my-scripts/filename.sh واذا كنت في ذلك المجلد الذي يحتوي الملف يكفي أن تكتب بدل المسار './' dot-slash والتي تعني الدليل الحالي مثلاً ./filename.sh واذا كان الدليل الحالي أي النقطة '.' موجود ضمن المسارات $PATH كما هو الحال عادةً فيمكنك كتابة اسم الملف filename.sh ولكن يفضل أن تتعود على كتابة ./ حتى تتجنب الحالة التي يوجد فيها برنامج بهذا الاسم في مجلد له أولوية أعلى في ترتيب ال $PATH ، وإذا أردت أن ينفذ برنامجك بدون مقدمات يجب أن تضعه في احدى المجلدات المكتوبة في $PATH لتعرف هذه المجلدات اكتب

bash$ echo $PATH
/bin:/usr/bin:/sbin:/usr/sbin:/usr/X11R6/bin:.

6.2.3 البرنامج الأول

كل ما بعد رمز ال hash هو تعليق لا يتم تنفيذه كل سطر هو عبارة عن اما أمر مبني ضمن bash أو سلسلة من برامج لينكس التي تعلمناها سابقا مثل echo و mkdir و cp وغيرها من الأوامر لهذا يكون أول برنامج لنا هكذا

#!/bin/bash
# hello.sh : this is a hello-world script
echo "hello world"
خزن هذا البرنامج باسم hello.sh انظر النتيجة

لتعرف متغير اكتب اسم المتغير ثم $ ثم قيمة المتغير دون مسافات ولتعوض قيمة المتغير ضع $ قبل اسم المتغير مثلا يصبح برنامجنا بالشكل التالي

#!/bin/bash
# hello.sh : this is a hello-world script
str1="hello world"
echo "$str1"
خزن الملف مرة أخرى ثم نفذه ستحصل على نفس النتيجة، لاحظ معنى $ هو تعويض قيمة المتغير مكان اسمه هنا قيمة المتغير str1 هي hello world فيصبح معنى "$str1" بابدال str1 مكان hello world فيصبح الأمر هو echo "hello world" وقد وضعت "$str1" داخل علامة التنصيص لأن echo يجب أن تأخذ النص على شكل معامل واحد فإذا لم أضعها يصبح الأمر echo hello world وهذا خطأ

6.2.4 التنصيص القوي والضعيف

تسمى علامة التنصيص المزدوج التي استعملناها سابقا بالتنصيص الضعيف لأنه بدلا من أخذ كل ما هو بينها كما هو كما يوحي الاسم (التنصيص/الإقتباس) فهي تقوم على عمل بعض التعديلات أحيانا كما في حالة تعويض قيمة المتغير وهناك الكثير من الحالات الأخرى منها ما يسمى escaping وهو استخدام رمز خاص هو back-slah '\' متبوعاً بأحد ما يلي:
\n سطر جديد
\r العودة لبداية السطر الحالي للكتابة فوقه
\b backspace حذف حرف للوراء
\f formfeed صفحة جديدة
\a تصدر صوت alert
\t tab أي مسافة جدولة
\nnn تعني الرمز المقابل ل nnn حيث nnn رقم بالثماني مثلا 33 هي escape و 101 تعني حرف A
\xnn تعني الرمز المقابل ل nnn حيث nnn رقم بالست-عشري مثلا1b هي escape و 41 تعني حرف A
\ مع أي شيء آخر تعني الشيء الآخر نفسه مثلا
\\ تعني \ واحد
\" تعني " وليس نهاية التنصيص المزدوج
\' تعني ' وليس نهاية التنصيص المفرد
مثلا البرنامج التالي

#!/bin/bash
# hello.sh : this is another hello-world script
str1="hello world"
str2="hello world again"
echo -e "$str1\n$str2"
ستكون نتيجته
hello world
hello world again
كل واحدة على سطر منفصل بسبب ال \n

النوع الآخر من التنصيص هو التنصيص القوي ويكون عن طريق علامة التنصيص المفردة ' وهي الموجودة فوق حرف الطاء في لوحة المفاتيح الإنجليزية حيث لا يتم تعويض أي من المتغيرات مثلا

str1="hello world"
echo '$str1'
ستطبع ما بداخلها تماما أي $str1
tipتلميح

كيف تكتب أمر ليطبع it's good to see you مستخدماً علامة تنصيص مفردة.

هناك بعض المتغيرات الخاصة التي يعرفها bash منها
تتعلق بالمعاملات Parameters
المتغيرمعناه
$*كل المعاملات معاً
$@كل المعاملات منفصلات
$#عدد المعاملات
$Nالمعامل رقم N
تتعلق بالمهمات Process
المتغيرمعناه
$?حالة الخروج لآخر أمر
$$معرف المهمة PID
$!معرف المهمة لآخر أمر
$0اسم ملف البرنامج
وبمناسبة الحديث عن المعاملات يمكن الإفادة من xargs في تمرير المعاملات التي تشاء إلى أي برنامج عن طريق الدخل القياسي (بواسطة أنبوب "|" مثلاً).

6.2.5 تعويض ناتج تفيذ برنامج

إذا أردت أن تأخذ الخرج القياسي لبرنامج وتعويضه في مكان معين مثل قيمة متغير ويتم ذلك بوضع الأامر داخل علامة ` وهي الرمز الموجد عند حرف الذال العربي في لوحة المفاتيح الإنجليزية أو بوضع الأمر بين قوسين () مسبوقين ب $

مثلا برنامج whoami يكتب اسمك فإذا كنت تريد أن يقول لك hello ahmad اكتب البرنامج التالي

#!/bin/bash
# hello.sh : this is a hello-world script
echo "hello `whoami`"
#echo "hello $(whoami)"
انظر هذا البرنامج الذي يستخدم pwd والتي تعطي اسم المجلد الحالي
#!/bin/bash
# whereami.sh : this is a 'where am I' script
str1="`whoami`"
str2="`pwd`"
echo "$str1 is now viewing the \"$str2\" folder"
سيكون ناتج تنفيذه مثلاً
ahmad is now viewing the "/home/ahmad/my-scripts/" folder
يمكنك أن تطلب من المستخدم أن يتجاوب مع البرنامج بإدخال قيمة متغير بواسطة الأمر الداخلي read مثلاً read you_name ويمكنك إجراء بعض الحسابات على الأعداد الصحيحة والسلاسل النصية بأمر expr اكتب expr --help لترى ما هي العمليات التي يوفرها هذا الأمر يستقبل الأرقام والعمليات على شكل معاملات منفصلة(مسافة مثلاً) لهذا لا تنجح expr '12*2-7' والصواب expr 12 '*' 2 - 7 ولاحظ علامة التنصيص حول * لمنع bash من تعويضها بأسماء الملفات لنأخذ هذا المثال البسيط
#! /bin/bash
# rect-area.sh : a script to find area of rectangle
echo -n "Enter width: "
read width
echo -n "Enter height: "
read height
area=`expr $width '*' $height`
echo "Area of rectangle=${width}x${height}=$area"
هذا البرنامج يسأل عن الطول والعرض ثم يحسب مساحة المستطيل مثلاً لوجربنا 15 ، 3 سيكون الناتج
bash$ ./rect-area.sh
Enter width: 15
Enter height: 3
Area of rectangle=15x3=45
bash$
لاحظ استعمال {} حول اسم المتغير بهذا نقول ل bash أن x ليست تابعة لاسم المتغير أي أن اسم المتغير ليسwidthx طبعاً يمكن تجنب هذا بوضع مسافة. يمكننا أيضاً في bash ولكن ليس sh أن نستفيد من الأمر let ضع let "area = width * height" مكان السطر حيث area=`expr $width '*' $height`. يمكن أيضاً استخدام اسلوب التعويض الحسابي وهو وضع قوسين مسبوقين ب ‘$‘ وهنا لا ضرورة لعلامة التنصيص area = $(($width*$height)) العمليات التي يمكن أن يقوم بها BASH هي (وهي المألوفة لمبرمج سي)
# from BASH INFO
`ID++ ID--'
     variable post-increment and post-decrement
`++ID --ID'
     variable pre-increment and pre-decrement
`- +'
     unary minus and plus
`! ~'
     logical and bitwise negation
`**'
     exponentiation
`* / %'
     multiplication, division, remainder
`+ -'
     addition, subtraction
`<< >>'
     left and right bitwise shifts
`<= >= < >'
     comparison
`== !='
     equality and inequality
`&'
     bitwise AND
`^'
     bitwise exclusive OR
`|'
     bitwise OR
`&&'
     logical AND
`||'
     logical OR
`expr ? expr : expr'
     conditional evaluation
`= *= /= %= += -= <<= >>= &= ^= |='
     assignment
`expr1 , expr2'
     comma
ولكن bash لا يقوم بحسابات الفاصلة العائمة (الكسور غير الصحيحة) لهذا يمكن أن نستعين ببرنامج bc بحيث يصبح السطر ذاته area=`echo "$width * $height" | bc` أو برنامج dc مثل نص لتحليل عدد لعوامله الأولية راجع فصل البرامج العلمية
#!/bin/bash
# fact.sh factroize an integer to primes
echo -n "Enter an integer: "
read n
echo "$n[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr\
[dli%0=1lrli2+dsi!>.]ds.xd1<2" | dc

6.2.6 الجمل الشرطية

قبل أن نبدأ بالجمل الشرطية لنتذكر أن البرامج التي ننفذها تعيد رقم يمثل حالتان هما النجاح (0) والفشل (أي رقم غير الصفر وهو يمثل سبب الفشل) وهذا المنطق مقلوب بعض الشيء لأن العادة في لغات البرمجة أن الصفر خطأ و غير ذلك(1 مثلاً) صواب. هذا يجعل الشرط في bash غامضاً لدرء الغموض نستعمل هنا نجاح success لنرمز للصفر و فشل fail لغير الواحد . انظر هذا المثال يبحث عن كلمة vfat في ملف fstab

bash$ grep -e vfat /etc/fstab >/dev/null; echo $?
0
لاحظ أن الصفر يعني أنه نجح أي وجدها. هنا وبالرجوع للجدول السابق ‘$؟‘ تعني حالة الخروج

أسهل طرق الشرط هو استعمال && و || الأولى تنفذ ما بعدها إذا نجح ما قبلها والأخرى إذا لم ينجح

bash$ grep -e vfat /etc/fstab >/dev/null &&
>	echo "yorika! I found It." || 
>	echo "good luck! no FAT found" 
yorika! I found It.
لاحظ إذا لم تتبعهما بأمر فإنه تلقائياً يتوقع سطراً آخر. يوجد في bash فحوصات منطقية وحسابية وملفية! أقصد على الملفات. هذه الأخيرة تمكنك من التأكد من وجود ملف أو من أنه مجلد أو ... وهي تضع بين أقواس مربعة
# File related checks
# From bash info page
`-a FILE' `-e FILE'
     True if FILE exists.
`-b FILE'
     True if FILE exists and is a block special file.
`-c FILE'
     True if FILE exists and is a character special file.
`-d FILE'
     True if FILE exists and is a directory.
`-f FILE'
     True if FILE exists and is a regular file.
`-g FILE'
     True if FILE exists and its set-group-id bit is set.
`-h FILE'
     True if FILE exists and is a symbolic link.
`-k FILE'
     True if FILE exists and its "sticky" bit is set.
`-p FILE'
     True if FILE exists and is a named pipe (FIFO).
`-r FILE'
     True if FILE exists and is readable.
`-s FILE'
     True if FILE exists and has a size greater than zero.
`-t FD'
     True if file descriptor FD is open and refers to a terminal.
`-u FILE'
     True if FILE exists and its set-user-id bit is set.
`-w FILE'
     True if FILE exists and is writable.
`-x FILE'
     True if FILE exists and is executable.
`-O FILE'
     True if FILE exists and is owned by the effective user id.
`-G FILE'
     True if FILE exists and is owned by the effective group id.
`-L FILE'
     True if FILE exists and is a symbolic link.
`-S FILE'
     True if FILE exists and is a socket.
`-N FILE'
     True if FILE exists and has been modified since it was last read.
`FILE1 -nt FILE2'
     True if FILE1 is newer (according to modification date) than
     FILE2, or if FILE1 exists and FILE2 does not.
`FILE1 -ot FILE2'
     True if FILE1 is older than FILE2, or if FILE2 exists and FILE1
     does not.
`FILE1 -ef FILE2'
     True if FILE1 and FILE2 refer to the same device and inode numbers.
مثلاً
bash$ [ -e /etc/fstab ] &&
>	echo "you have an 'fstab' so what!" || 
>	echo "Ooops! where is your fstab" 
you have an 'fstab' so what!
لدينا بعد المقارنات (المتباينات) أكبر وأصغر ... وهي أيضا تستعمل الأقواس المربعة ‘[ ]‘
`INT1 -eq INT2'
`INT1 -ne INT2'
`INT1 -lt INT2'
`INT1 -le INT2'
`INT1 -gt INT2'
`INT1 -ge INT2'
     return true if INT1 is equal to, not equal to, less than,
     less than or equal to, greater than, or greater
     than or equal to INT2, respectively.
`-z STRING'
     True if the length of STRING is zero.
`-n STRING'
`STRING'
     True if the length of STRING is non-zero.
`STRING1 == STRING2'
     True if the strings are equal.  `=' may be used in place of `=='
     for strict POSIX compliance.
`STRING1 != STRING2'
     True if the strings are not equal.
`STRING1 < STRING2'
     True if STRING1 sorts before STRING2 lexicographically in the
     current locale.
`STRING1 > STRING2'
     True if STRING1 sorts after STRING2 lexicographically in the
     current locale.
يمكن دمج أكثر من شرط باستعمال ‘أو‘ من خلال -o أو ‘و‘ من خلال -a

صيغة جملة if هي كما يلي

if TEST-COMMANDS; then
	CONSEQUENT-COMMANDS;
elif TEST-COMMANDS; then
	CONSEQUENT-COMMANDS;
else	CONSEQUENT-COMMANDS;
fi
حيث elif و اختيارية ويمكن أن تتكرر وهي تعني else if أي "وإلا هل ..." كذلك else التي تعني "وإلا" ولك الأخيرة قد تظهر لمرة واحدة على الأكثر. أما fi فهي if مقلوبة وتعني end if. أما للاختيار بين عدة احتمالات قد تففر عليك الكثير من جمل elif باستعمال case
case WORD in
    PATTERN )	CONSEQUENT-COMMANDS ;;
    PATTERN1 | PATTERN2 )
		CONSEQUENT-COMMANDS ;;
esac
# PATTERN can have * ? [] .. etc
esac فهي case مقلوبة وتعني end case. وفيهما (أي if و case) يمكن استعمال الشرط بالأقواس المربعة ويمكن إستعمال && و || لعمل شرط مركب ‘و‘ و ‘أو‘ كما كما يمكن نفي الشرط ب ! قبل الشرط استعمال الأقواس. أيضاً يمكن استعمال شرط بالعمليات الحسابية داخل قوسين (صفر نجاح، غيره فشل) مثلاً
#!/bin/bash
# if.sh: a sample if statment script
read a
read b
if (( a < b)); then
	echo "$a is less than $b.";
else	echo "$a is greater than or equal to $b.";
fi
مثال آخر يحسب إذا كان العدد زوجي(باقي القسمة على 2 يساوي صفر) أم فردي
#!/bin/bash
# oddeven.sh: tell if a given number odd or even
  echo "enter a number"
  h=`read`
  let "remainder = h % 2"
  if [ "$remainder" -eq 0 ]   # Even?
  then
    echo "$h is even"
  else
    echo "$h is odd"
  fi
هذا مثال على case
#!/bin/bash
# case.sh: a sample case statement script
echo -n "enter a color : "
read c
case $c in
    blue )
	echo "I like blue too.";;
    red )
	echo "red is not bad.";;
    green )
	echo "green is good.";;
    yellow | orange | cyan | magenta )
	echo "$c is not a basic color.";;
    * )
	echo "is $c a real color?";;
esac
تكمن قوة case في أننا لا نتعامل مع سلاسل نصية فقط بل مع نماذج PATTERNS
# from BASH INFO
`*'
     Matches any string, including the null string.
`?'
     Matches any single character.
`[...]'
     Matches any one of the enclosed characters.  A pair of characters
     separated by a hyphen denotes a RANGE EXPRESSION; any character
     that sorts between those two characters, inclusive, using the
     current locale's collating sequence and character set, is matched.
     If the first character following the `[' is a `!'  or a `^' then
     any character not enclosed is matched.  A `-' may be matched by
     including it as the first or last character in the set.  A `]' may
     be matched by including it as the first character in the set.  The
     sorting order of characters in range expressions is determined by
     the current locale and the value of the `LC_COLLATE' shell
     variable, if set.

   If the `extglob' shell option is enabled using the `shopt' builtin,
several extended pattern matching operators are recognized.  In the
following description, a PATTERN-LIST is a list of one or more patterns
separated by a `|'.  Composite patterns may be formed using one or more
of the following sub-patterns:

`?(PATTERN-LIST)'
     Matches zero or one occurrence of the given patterns.
`*(PATTERN-LIST)'
     Matches zero or more occurrences of the given patterns.
`+(PATTERN-LIST)'
     Matches one or more occurrences of the given patterns.
`@(PATTERN-LIST)'
     Matches exactly one of the given patterns.
`!(PATTERN-LIST)'
     Matches anything except one of the given patterns.

6.2.7 الحلقات التكرارية

هذا ملخص للحلقت التكرارية في bash

until TEST-COMMANDS; do CONSEQUENT-COMMANDS ; done
while TEST-COMMANDS; do CONSEQUENT-COMMANDS ; done
for NAME in WORDS;  do CONSEQUENT-COMMANDS ; done
for (( EXPR1 ; EXPR2 ; EXPR3 )) ;  do CONSEQUENT-COMMANDS ; done
#you may use 'break' or 'continue'
أكثرها شيوعاً هي الحلقة التكرارية for التي يأتي بده متغير دون $ ثم تأتي قائمة بالقيم التي سيأخذها ويمكن استغلال خاصية تعويض أسماء الملفات كما في هذا المثال الذي يبدل كل ملفات في الدليل الحالي بنسخة مضغوطة منه
#!/bin/sh
# gz-all.sh: replace all files with GZIPed version
for i in ./*
do
	gzip -9 "$i"
done
المثال التالي يوضح عداد بطريقة سي
#!/bin/bash
# c-for.sh: c-like for loop
echo "this is a counter"
MAX=5
for ((i=1; i<=MAX; i++))
do
echo "$i"
done

6.2.8 الوظائف

الأجزاء التي تتكرر كثيراً في برنامج ما يمكن وضعها في وظيفة function يتم استدعؤها call عند الضرورة وتمرير لها بعض المعاملات وهي متغيرات يفترض أن تحدد سلوكات مختلفة لنفس الوظيفة. فيما يسمى بأسلوب البرمجة الهيكلية حيث يقسم البرنامج إلى أجزاء كل منها يقوم بمهمة بسيطة ولكن بكفاءة مما يسهل تتبع البرنامج لأن المتغيرات المحلية داخل تلك الوظئيفة لا تعتمد قيمتها على الأجزاء الخارجية ولأن ذلك يسمح لك بتجربة(فحص) كل وظيفة بشكل مستقل. عمل وظيفة يكون بوضع () بعد اسم الوظيفة ووضع الأوامر بين {} و يمكن أن تسبق اسم الوظيفة بكلمة function (إذا أردت).

#! /bin/sh
# func.sh : a script using functions

TellTime()
{
	echo "It's `date +%I:%M` now"
}
function SayHello()
{
	echo "Hello `whoami`"
	echo "It's a nice day"
}

# this is the main/global part
SayHello
TellTime
لاحظ أن استدعاء الوظيفة يتم من خلال ذكر اسمها وكأنها برنامج أو أمر. مثال مساحة المستطيل السابق يمكن أن يصبح
#! /bin/sh
# rect-area.sh : a script to find area of rectangle

# this function calc the rectangle area
RectArea()
{
	width="$1"
	height="$2"
	area=`echo "$width * $height" | bc`
	echo "$area"
}
# this is the main/global part
echo -n "Enter width: "
read width
echo -n "Enter height: "
read height
area=`RectArea $width $height`
echo "Area of rectangle=${width}x${height}=$area"
لاحظ أن الوظيفة استقبلت المعامل الأول أي الطول من خلال المتغير $1 والعرض $2 وليس معامل تنفيذ الscript كله

6.2.9 الملفات المتعددة

يمكنك أن تضع الأجزاء المكرر أو الوظائف التي تستعملها بكثر في ملف منفصل (مكتبة) وتستدعيه في ملف برنامجك وهذه الطريقة تسمى source وهي تشبه عملية include في سي. تتم هذه العملية بكتابة source FILE أو . FILE تستخدم هذه الطريقة بكثرة في نصوص الإقلاع و الخدمات /etc/rc.d حيث يوجد ملف اسمه functions يحتوي على الوظائف الشائعة التي تقوم بها الخدمات عند نجاح المهمة (يكتب كلمة OK في مكان معين باللون الأخضر) أو فشلها (يكتب FAIL باللون الأحمر). هذا المثال يوضح ذلك

# rect-tool.sh: this is a tool lib for rect-client.sh
# it need not be executable (chmod -x)

function RectArea()
{
	width="$1"
	height="$2"
	area=$(echo "$width * $height" | bc)
	echo "$area"
}

#!/bin/bash # rect-client.sh: main file of rectangle project! . ./rect-tool.sh # this is the main/global part echo -n "Enter width: " read width echo -n "Enter height: " read height area=$(RectArea $width $height) echo "Area of rectangle=${width}x${height}=$area"

6.2.10 تطبيقات

البرنامج التالي يبحث عن الأقراص المدمجة ويضع الوصلات links المناسبة في دليل dev. هذا البرنامج أستعمله في التوزيعات القديمة التي لا تتعرف على الأجهزة المضافة تلقائياً فأجعل هذا البرنامج ينفذ كلما أقلع لينكس. فيصبح /dev/cdrom يشير إلى /dev/hdb مثلاً

#!/bin/sh
# findcds.sh: find IDE cdroms and fix the lins in /dev
if [ "$UID" = "0" ];then
	echo "Error: You must be a root run 'su' 1st."
	exit 1
fi
[ ! -d /proc/ide ] && mount none /proc -t proc || exit 1
echo -n "finding IDE CDROMs : "
cds=""
for i in hda hdb hdc hdd hde hdf hdg hdi hdj
do
    echo -n "."
    if [ -f /proc/ide/$i/media ];then
	dv=`cat /proc/ide/$i/media`
        [ "cdrom" = "$dv" ] && cds="$cds $i"
    fi
    echo -n "."
done
echo " OK. [$cds ]"
CDS_LIST="`echo "$cds " | sed -e 's/^ //'`"
echo -n -e "updating /dev/cdrom link ... "
CD=`echo "$CDS_LIST" | cut -d ' ' -f 1`
echo "/dev/$CD"
ln -sf "/dev/$CD" /dev/cdrom
CDS_LIST=`echo "$CDS_LIST" | cut -d ' ' -f 2-`
N="2"
for i in $CDS_LIST
do
    echo -n -e "updating /dev/cdrom$N link ... "
    echo "/dev/$i"
    ln -sf "/dev/$i" "/dev/cdrom$N"
    N=`expr $N + 1`    
done
أولاً يتأكد من أن المستخدم هو الجذر بجملة if تقليدية من خلال فحص قيمة UID التي تكون صفر للجذر. الخطوة التالية تفحص وجود الدليل /proc/ide وذلك بعملية && التي لا تنفذ ما بعدها إلا إذا نجح ما قبلها. فإذا لم يكن موجوداً هذا يعني أنه غير مضموم عندها قم بضمه. تذكر ما قلناه عن proc وهو الدليل الذي يقدم المعلومات التي تعرفها النواة. الآن لكل جهاز ide انظر محتويات الملف media فإذا كان cdrom فإنه فإنه قرص مدمج عنده أضفه إلى متغير cds. الآن يأتي عمل links الخاصة بأول واحد إلى /dev/cdrom والباقيات أعطها رقم مثلاً /dev/cdrom2.

البرنامج التالي استخدمه في توليد قائمة المصطلحات فهو يعمل على حذف كل شيء ليس إنجليزي أو أرقام و ‘-‘ و ‘\‘ و ‘/‘ وتحويلها إلى سطر جديد لهذا تصبح كل كلمة الإنجليزية(أو مصطلح مثل apt-get) في سطر منفصل ثم حذف الأسطر التي لا تحتوي أحرف ثم ترتيبها أبجدياً وبقى تحريره وتنظيفه يدوياً.

#!/bin/sh
    rm /tmp/glassory.txt.tmp 2>/dev/null
for i in ./*.html
do
echo -n "processing [$i] ...    "
    cat $i | sed -e "s/[^0-9A-Za-z\\\/\-]/+/g" | 
    tr A-Z a-z | tr -s "+" "\012" |
    sed -e "s/^[0-9\\\/ ]*//g" >> /tmp/glassory.txt.tmp
echo " OK "
done
echo -n "sorting ... "
    cat /tmp/glassory.txt.tmp | sort | uniq > ./glassory.txt
    rm /tmp/glassory.txt.tmp
echo "done!"

إذا كان البرنامج يقوم بعملية بتخزين ملفات مؤقتة عليه حذفها قبل الخروج.فإن خرج بضغط CTRL+C أو بأداة kill فإنه لن يصل للجزء الذي يقوم بحذف الملفات الزائدة. بعض النصوص البرمجية تقوم بحفظ نسخة احتياطية من ملفات الإعدادات قبل أن تبدأ ثم تعدلها فإن فشلت تعيد القديمة ولكن ماذا لو خرج المستخدم قبل إتمام الحسابات دون أن يستعيد النسخة الاحتياطية. حالة أخرى هي بأنك تريد إهمال بعض الإشارات التي يرسلها المستخدم والكثير من التطبيقات الأخرى التي تتطلب تتبع استقبال أي إشارة. يفور bash طريقة مرنة لذلك مثلاً لحذف ملف عند استلام إشارة الإنهاء بوساطة لوحة المفاتيح أو kill فإن الأمر

trap "rm /tmp/mytmp" 2 3 9
المثال التالي يطلب منك إدخال نص وفي هذه الأثناء يطبع رسالة عند تلقيه اشارة بواسطة CTRL+Z أو CTRL+C أو kill
#!/bin/bash
echo "Enter something, or try to send me a signal - CTRL+C -"
# 18 20 24 and 17 19 23 for CTRL+Z
# 2 3 9 for CTRL+C and kill -KILL
trap "echo 'Hi, we have a signal'" 18 20 24 17 19 23 2 3 9
read a
الأرقام المقابلة لكل إشارة يمكنك أن تحصل عليها من signal(7) مثلاً بكتابة man 7 signal

انظر BASH info pages و Advanced Bash-Scripting Guide


<< السابق كتاب لينكس الشامل التالي >>