الاستخدامات المتقدمة لعبارة if الشرطية في Bash

من موسوعة حسوب
مراجعة 17:38، 29 أغسطس 2018 بواسطة أسامه-دمراني (نقاش | مساهمات) (إدخال 2.1 إضافة محتوى من المصدر والتصنيفات وتنسيق المحتوى)


بُنى 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...

يمكن اختيار رموز حالة الخروج بُحرِّية كما ترى، وعادة ما تكون لأوامر الخروج سلسلة من رموز محددة، انظر دليل المبرمج لكل أمر من أجل المزيد من المعلومات.

انظر أيضًا

مصادر