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
تلميح
عند النقر المزدوج على ملفات ذات الامتداد الخاص بلغة تفسيرية
مثل 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
تلميح
كيف تكتب أمر ليطبع 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
|