الاستخدامات المتقدمة لعبارة if الشرطية في Bash
بُنى if/then/else
يوضح المثال التالي البُنية التي يجب استخدامها لاتخاذ إجراء أو سلسلة إجراءات إن تحققت شروط عبارة if
، وسلسلة إجراءات أخرى إن لم تتحقق:
hsoub scripts> gender="male"
hsoub scripts> if [[ "$gender" == "f*" ]]
More input> then echo "Pleasure to meet you, Madame."
More input> else echo "How come the lady hasn't got a drink yet?"
More input> fi
How come the lady hasn't got a drink yet?
hsoub scripts>
الفرق بين
[]
و[[]]
على عكس
]
، فإن]]
تمنع انقسام الكلمات في قيَم المتغيرات، لذا إن كانت"VAR="var with spaces
فلن تحتاج أن تضعVAR$
بين علامات تنصيص مزدوجة في اختبار ما، رغم أن استخدام علامات التنصيص يظل سلوكًا أفضل على أي حال. كذلك تمنع]]
توسُّع اسم المسار (pathname expansion)، لذا لا تحاول المقاطع النصية التي فيها حروف البدل (wildcards) أن تتوسع إلى أسماء الملفات. أيضًا فإن استخدام]]
و==
و=!
يفسر المقاطع التي على اليمين على أنها أنماط تجميع (glob patterns) تُقارَن بالقيم التي على اليسار، فمثلًا[[ *value" == val" ]]
.
قد تتكون قائمة الأوامر التابعة البديلة ALTERNATE-CONSEQUENT-COMMANDS
التي تتبع عبارة else
من أي أمر لنظام يونكس يعيد حالة خروج، تمامًا مثل قائمة الأوامر التابعة CONSEQUENT-COMMANDS
التي تلي عبارة then
. إليك مثالًا آخر يكمّل المثال المشروح في اختبار حالة الخروج من فصل مقدمة إلى if
في Bash:
hsoub ~> su -
Password:
[root@alaraby root]# if ! grep ^$USER /etc/passwd 1> /dev/null
> then echo "your user account is not managed locally"
> else echo "your account is managed from the local /etc/passwd file"
> fi
your account is managed from the local /etc/passwd file
[root@alaraby root]#
في المثال السابق، غيَّرنا المستخدم إلى المستخدم الجذر root
لترى أثر عبارة else
، ذلك أن المستخدم الجذر الخاص بك هو مستخدِم محلي، على عكس حساب المستخدم الخاص بك، إذ قد يكون خاضعًا لنظام مركزي، مثل خادم LDAP.
فحص وسائط سطر الأوامر
من الأنسب وضع القيَم للمتغيرات في سطر الأوامر بدلًا من ضبط المتغير على قيمة ثم تنفيذ البرنامج، ونستخدم المعامِلات الموضعية 1$
، 2$
، … ، N$
لهذا الغرض، حيث يشير #$
إلى عدد وسائط سطر الأوامر، و0$
إلى اسم برنامج الصدفة. إليك مثالًا بسيطًا يشرح ذلك:
hsoub@alaraby:~/testdir$ cat penguin.sh
#!/bin/bash
# (Tux) هذا البرنامج يسمح لك بتقديم أطعمة إلى البطريق
# لن يكون البطريق سعيدًا إلا حين يُعطى سمكة
if [ "$1" == fish ]; then
echo "Hmmmm fish... Tux is happy!"
else
echo "Tux doesn't like that. Tux wants fish!"
fi
hsoub@alaraby:~/testdir$ penguin.sh apple
Tux doesn't like that. Tux wants fish!
hsoub@alaraby:~/testdir$ penguin.sh fish
Hmmm fish... Tux is happy!
hsoub@alaraby:~/testdir$
إليك مثالًا آخر يستخدم معامليْن موضعيين:
hsoub ~> cat weight.sh
#!/bin/bash
# يطبع هذا البرنامج رسالة عن وزنك إن أعطيته وزنك بالكيلو جرام وطولك بالسنتيمتر
weight="$1"
height="$2"
idealweight=$[$height - 110]
if [ $weight -le $idealweight ] ; then
echo "You should eat a bit more fat."
else
echo "You should eat a bit more fruit."
fi
hsoub ~> bash -x weight.sh 55 169
+ weight=55
+ height=169
+ idealweight=59
+ '[' 55 -le 59 ']'
+ echo 'You should eat a bit more fat.'
You should eat a bit more fat.
اختبار عدد الوسائط
يشرح المثال التالي كيفية تعديل المثال السابق ليطبع رسالة في حالة إدخال أكثر من معامليْن موضعيين:
hsoub ~> cat weight.sh
#!/bin/bash
# يطبع هذا البرنامج رسالة عن وزنك إن أدخلت له وزنك بالكيلو جرام وطولك بالسنتيمتر
if [ ! $# == 2 ]; then
echo "Usage: $0 weight_in_kilos length_in_centimeters"
exit
fi
weight="$1"
height="$2"
idealweight=$[$height - 110]
if [ $weight -le $idealweight ] ; then
echo "You should eat a bit more fat."
else
echo "You should eat a bit more fruit."
fi
hsoub ~> weight.sh 70 150
You should eat a bit more fruit.
hsoub ~> weight.sh 70 150 33
Usage: ./weight.sh weight_in_kilos length_in_centimeters
في المثال السابق، أشار 1$
إلى المعامل الأول، و 2$
إلى الثاني، وهكذا، وخُزِّن عدد الوسائط في #$
. انظر لطباعة رسائل الاستخدام بشكل أفضل.
اختبار وجود ملف ما
يُستخدم هذا الاختبار في برامج كثيرة لضمان تنفيذ تلك البرامج، إذ لا فائدة من استدعاء برنامج إن كنت تعلم أنه لن يُنفَّذ:
#!/bin/bash
# يعطي هذا البرنامج بيانات عن ملف ما.
FILENAME="$1"
echo "Properties for $FILENAME:"
if [ -f $FILENAME ]; then
echo "Size is $(ls -lh $FILENAME | awk '{ print $5 }')"
echo "Type is $(file $FILENAME | cut -d":" -f2 -)"
echo "Inode number is $(ls -i $FILENAME | cut -d" " -f1 -)"
echo "$(df -h $FILENAME | grep -v Mounted | awk '{ print "On",$1", \
which is mounted as the",$6,"partition."}')"
else
echo "File does not exist."
fi
لاحظ أن الملف قد أشير إليه باستخدام متغير وهو أول وسيط (argument) للبرنامج في تلك الحالة، أما حين لا يكون لدينا وسائط فإن أماكن الملفات تُحفظ في متغيرات في أول البرنامج، ويشار إلى محتواها باستخدام تلك المتغيرات، وهكذا لن تحتاج إلى تعديل اسم ملف في برنامج إلا مرة واحدة.
أسماء الملفات التي تحتوي على مسافات
يفشل المثال السابق إن كانت قيمة
1$
يمكن تحليلها ككلمات متعددة، ويمكن إصلاح أمرif
في تلك الحالة باستخدام علامات تنصيص مزدوجة حول اسم الملف أو باستخدام]]
بدلًا من]
.
بُنى if/then/elif/else
الصورة الكاملة لعبارة if
هي:
if TEST-COMMANDS; then
CONSEQUENT-COMMANDS;
elif MORE-TEST-COMMANDS; then
MORE-CONSEQUENT-COMMANDS;
else ALTERNATE-CONSEQUENT-COMMANDS;
fi
تُنفَّذ أوامر الاختبار TEST-COMMANDS
أولًا فإن كانت حالة الإعادة (return status) لها صفرية فإن الأوامر التابعة تُنفَّذ CONSEQUENT-COMMANDS
، لكن إن كانت حالة الإعادة لأوامر الاختبار غير صفرية فعندها تُنفَّذ قوائم elif
بالترتيب، فإن كانت حالة الإعادة لإحداها صفرية فإن قائمة "المزيد من الأوامر التابعة" MORE-CONSEQUENT-COMMANDS
التي ترافقها تُنفذ ويكتمل الأمر بهذا.
وإن تُبعت else
بقائمة أوامر تابعة بديلة ALTERNATE-CONSEQUENT-COMMANDS
وكانت حالة الأمر الأخير في آخر شرط لعبارة if
أو elif
غير صفرية، فإن قائمة الأوامر التابعة البديلة تُنفذ، وتكون حالة الإعادة عندها هي حالة الخروج لآخر أمر يُنفَّذ أو صفرًا إن لم يتحقق أي شرط من الشروط المعطاة في الأوامر.
مثال
إليك مثالًا يمكنك وضعه في ملف crontab
الخاص بك للتنفيذ اليومي:
hsoub /etc/cron.daily> cat disktest.sh
#!/bin/bash
# يُجري هذا البرنامج اختبارًا بسيطًا لفحص مساحة القرص الصلب.
space=`df -h | awk '{print $5}' | grep % | grep -v Use | sort -n | tail -1 | cut -d "%" -f1 -`
alertvalue="80"
if [ "$space" -ge "$alertvalue" ]; then
echo "At least one of my disks is nearly full!" | mail -s "daily diskcheck" root
else
echo "Disk space normal" | mail -s "daily diskcheck" root
fi
عبارات if المتداخلة
يمكن استخدام عبارة if
داخل عبارة if
أخرى بأي عدد من المستويات التي ترغب فيها طالما يمكنك التحكم فيها، إليك مثالًا يُجري اختبارًا للبحث عن السنوات الكبيسة (leap years):
hsoub ~/testdir> cat testleap.sh
#!/bin/bash
# سيتفقد هذا البرنامج السنة الحالية لنعرف ما إن كنا في سنة كبيسة.
year=`date +%Y`
if [ $[$year % 400] -eq "0" ]; then
echo "This is a leap year. February has 29 days."
elif [ $[$year % 4] -eq 0 ]; then
if [ $[$year % 100] -ne 0 ]; then
echo "This is a leap year, February has 29 days."
else
echo "This is not a leap year. February has 28 days."
fi
else
echo "This is not a leap year. February has 28 days."
fi
hsoub ~/testdir> date
Tue Jan 14 20:37:55 CET 2003
hsoub ~/testdir> testleap.sh
This is not a leap year.
العمليات المنطقية
يمكن إيجاز البرنامج السابق باستخدام المعامِلات المنطقية AND (&&
)، و OR (||
).
#!/bin/bash
# يتفقد هذا البرنامج لنعرف ما إن كنا في سنة كبيسة
year=`date +%Y`
if (( ("$year" % 400) == "0" )) || (( ("$year" % 4 == "0") && ("$year" % 100 != "0") )); then
echo "This is a leap year. Don't forget to charge the extra day!"
else
echo "This is not a leap year."
fi
استخدمنا الأقواس المزدوجة في المثال السابق لاختبار تعبير حسابي، انظر التوسع الحسابي في فصل التوسعات في Bash، يكافئ هذا عبارة let
. لا تستخدم الأقواس المربعة هنا أو تحاول تجربة شيء مثل [ 400 % year$]$
، ذلك أن الأقواس المربعة هنا لا تمثل أمرًا حقيقيًا في نفسها. كذلك، ربما تود استخدام محررات النصوص التي تدعم أنظمة الألوان المختلفة وفق اللغة التي تكتبها، ذلك أنها مفيدة لتحديد الأخطاء في شيفرتك، أحد تلك المحررات هو gvim لكن ستجد أيضًا kwrite وغيره.
استخدام عبارة exit و if
تعرفنا على عبارة exit
قبل قليل، وهي تنهي تنفيذ البرنامج بالكامل، وتستخدم غالبًا إن كان الطلب الذي طلبه المستخدم خاطئًا، أو لم تُنفَّذ عبارة بنجاح أو إن حدث خطأ آخر. وتأخذ عبارة exit
وسيطًا اختياريًا (optional argument)، ويكون ذلك الوسيط هو الرمز العددي لحالة الخروج، والذي يُمرر مرة ثانية إلى الصدفة الأم ويُخزَّن في متغير ?$
. وعندما تكون قيمة الوسيط صفرًا فهذا يعني أن البرنامج نُفِّذ بنجاح، وقد تُستخدم أي قيمة أخرى من قِبل المبرمجين ليمرروا رسائل مختلفة إلى الصدفة الأم كي تُتَّخذ إجراءات مختلفة وفقًا لفشل أو نجاح العملية الفرعية، أما إن لم يُعط وسيط لأمر exit
فإن الصدفة الأم تستخدم القيمة الحالية لمتغير ?$
.
إليك مثالًا لبرنامج penguin.sh
الذي تقدَّم شرحه مع قليل من التعديل بحيث يرسل حالة خروجه إلى الصدفة الأم التي فيها برنامج feed.sh
:
hsoub ~/testdir> cat penguin.sh
#!/bin/bash
# (Tux) هذا البرنامج يسمح لك بتقديم أطعمة إلى البطريق
# لن يكون البطريق سعيدًا إلا حين يُعطى سمكة
# لقد أضفنا أيضًا خياري الدولفين والجمل.
if [ "$menu" == "fish" ]; then
if [ "$animal" == "penguin" ]; then
echo "Hmmmmmm fish... Tux is happy!"
elif [ "$animal" == "dolphin" ]; then
echo "Pweetpeettreetppeterdepweet!"
else
echo "*prrrrrrrt*"
fi
else
if [ "$animal" == "penguin" ]; then
echo "Tux doesn't like that. Tux wants fish!"
exit 1
elif [ "$animal" == "dolphin" ]; then
echo "Pweepwishpeeterdepweet!"
exit 2
else
echo "Will you read this sign?!"
exit 3
fi
fi
يُستدعى هذا البرنامج في المثال التالي الذي يصدر متغيراته menu
و animal
:
hsoub ~/testdir> cat feed.sh
#!/bin/bash
# penguin.sh هذا البرنامج يتصرف بناءً على حالة الخروج التي يعطيها
export menu="$1"
export animal="$2"
feed="/nethome/anny/testdir/penguin.sh"
$feed $menu $animal
case $? in
1)
echo "Guard: You'd better give'm a fish, less they get violent..."
;;
2)
echo "Guard: It's because of people like you that they are leaving earth all the time..."
;;
3)
echo "Guard: Buy the food that the Zoo provides for the animals, you ***, how
do you think we survive?"
;;
*)
echo "Guard: Don't forget the guide!"
;;
esac
hsoub ~/testdir> ./feed.sh apple penguin
Tux doesn't like that. Tux wants fish!
Guard: You'd better give'm a fish, less they get violent...
يمكن اختيار رموز حالة الخروج بُحرِّية كما ترى، وعادة ما تكون لأوامر الخروج سلسلة من رموز محددة، انظر دليل المبرمج لكل أمر من أجل المزيد من المعلومات.