کدهای خروج
کدهای خروج اعدادی بین صفر و ۲۵۵ میباشند، که هر یک از فرمانهای یونیکس وقتی کنترل را به پردازش والد خود میدهد، برمیگرداند.اعداد دیگر میتوانند استفاده شوند، اما با آنها نسبت به ۲۵۶ رفتار میشود، بنابراین
exit -10
برابر است با exit 246
و exit 257
معادل exit 1
است.
این کدها میتوانند در یک اسکریپت پوسته برای تغییر جریان اجرا نسبت به موفقیت یا شکست دستور اجراشده، استفاده گردند. این موضوع به طور خلاصه در بخش متغیرها - قسمت دوم معرفی شده است. در اینجا به طور مفصلتر به تفسیرهای معتبر کدهای خروج نگاه خواهیم نمود.
موفقیت به طور سنتی با exit 0
نمایندگی میشود عدم موفقیت به طور معمول با یک کد غیر صفر نشان داده میشود. این مقدار میتواند بیانگر دلایل متفاوت عدم موفقیت باشد.
برای مثال، grep
گنو در موفقیت 0
را برمیگرداند، 1
اگر مورد انطباقی پیدا نشود، و 2
برای سایر خطاها( خطای دستوری، عدم وجود فایلها، و غیره ).
ما به سه راهکار برای کنترل وضعیتهای خطا، نگاه خواهیم نمود، و جنبهها و رموز هریک از رویکردها را بحث خواهیم کرد.
در مرحله اول، راهکار ساده:
#!/bin/sh # First attempt at checking return codes USERNAME=`grep "^${1}:" /etc/passwd|cut -d":" -f1` if [ "$?" -ne "0" ]; then echo "Sorry, cannot find user ${1} in /etc/passwd" exit 1 fi NAME=`grep "^${1}:" /etc/passwd|cut -d":" -f5` HOMEDIR=`grep "^${1}:" /etc/passwd|cut -d":" -f6` echo "USERNAME: $USERNAME" echo "NAME: $NAME" echo "HOMEDIR: $HOMEDIR"
این اسکریپت اگر نام کاربری معتبر درفایل
/etc/passwd
به آن بدهید، خوب کار میکند.
هرچند، اگر یک نام نامعتبر وارد کنید، آنچه را که در ابتدا ممکن است انتظار داشته باشید، انجام نمیدهد، و فقط چنین نمایش میدهد:
USERNAME: NAME: HOMEDIR:چرا چنین است؟ به طوری که اشاره شد، متغیر
$?
برای بازگردانیدن کد وضعیت آخرین فرمان اجرا شده، تنظیم شده است. در این حالت، یعنی دستور cut
. فرمان cut
مشکلی نداشته که نیاز به گزارش احساس شود - تا وقتی که من بتوانم از بررسی و خواندن سند به او بگویم، فرمان cut
هر اتفاقی رخ دهد، صفر را برمیگرداند! یک رشته تهی به او تحویل شده و اوکارش را انجام داده - اولین فیلد از ورودیاش را بازگردانده،که فقط یک رشته تهی شده است.بنابراین چه باید بکنیم؟ اگر در اینجا یک خطا داشته باشیم،
grep
آن را گزارش خواهد کرد، نه cut
.
بنابراین ما باید کد برگشتی فرمان grep
را بررسی کنیم، نه کدوضعیت cut
را.
#!/bin/sh # Second attempt at checking return codes grep "^${1}:" /etc/passwd > /dev/null 2>&1 if [ "$?" -ne "0" ]; then echo "Sorry, cannot find user ${1} in /etc/passwd" exit 1 fi USERNAME=`grep "^${1}:" /etc/passwd|cut -d":" -f1` NAME=`grep "^${1}:" /etc/passwd|cut -d":" -f5` HOMEDIR=`grep "^${1}:" /etc/passwd|cut -d":" -f6` echo "USERNAME: $USERNAME" echo "NAME: $NAME" echo "HOMEDIR: $HOMEDIR"
این مشکل را برای ما برطرف میکند، گرچه با هزینه نوشتن کد کمی طولانیتر.
این یک روش اساسی است، که ممکن است در کتابهای درسی به شما نشان داده شود، اما آن را کنترل خطای اسکریپت پوسته دانستن، دور از عقلانیت است. این شیوه به خصوص برای توالی دستورات نمیتواند مناسبترین ،یا شاید غیرقابل حمایت باشد. در اینجا، ما دو راهکار جایگزین را وارسی میکنیم
در رویکرد دوم، میتوانیم، با قرار دادن کنترل در یک تابع جداگانه، به جای شلوغ کردن کد با تستهای بیشتر از ۴ سطر، تا حدی اینرا منظمتر کنیم:
#!/bin/sh # A Tidier approach check_errs() { # Function. Parameter 1 is the return code # Para. 2 is text to display on failure. if [ "${1}" -ne "0" ]; then echo "ERROR # ${1} : ${2}" # as a bonus, make our script exit with the right error code. exit ${1} fi } ### main script starts here ### grep "^${1}:" /etc/passwd > /dev/null 2>&1 check_errs $? "User ${1} not found in /etc/passwd" USERNAME=`grep "^${1}:" /etc/passwd|cut -d":" -f1` check_errs $? "Cut returned an error" echo "USERNAME: $USERNAME" check_errs $? "echo returned an error - very strange!"
این به ما اجازه میدهد بدون ۳ بار نوشتن جداگانه تستها، با سفارشی کردن پیغامهای خطا، با یکبار نوشتن روال تست، خطاها را سه مرتبه کنترل کنیم. میتوانیم هرچند مرتبه که بخواهیم، آن را فراخوانی کنیم، ایجاد اسکریپت هوشمندتر، با هزینه خیلی کم برای برنامهنویس. برنامه نویسان پرل تشابه این مورد با دستور
die
در پرل را تشخیص خواهند داد.به عنوان سومین راهکار، ما به یک شیوه سادهتر و پختهتر نگاه میکنیم. من متمایل به استفاده از این روش در ساختمان کرنلها میباشم - خودکارسازیهای ساده، که اگر همه چیز خوب باشد، فقط با آن پیش بروید، اما اگر چیزی اشتباه باشد، مستلزم انجام عمل هشمند کاربر میباشد( یعنی کاری که یک اسکریپت نمیتواند انجام دهد! ):
#!/bin/sh cd /usr/src/linux && \ make dep && make bzImage && make modules && make modules_install && \ cp arch/i386/boot/bzImage /boot/my-new-kernel && cp System.map /boot && \ echo "Your new kernel awaits, m'lord."این اسکریپت در سرتاسر وظایف متنوع درگیر با ساختن کرنل لینوکس( که میتواند کاملاً وقت گیر باشد ) به کار میرود، و از عملگر
&&
برای کنترل موفقیت استفاده میکند. برای انجام آن با if
اینطور گرفتارمیشدید:
#!/bin/sh cd /usr/src/linux if [ "$?" -eq "0" ]; then make dep if [ "$?" -eq "0" ]; then make bzImage if [ "$?" -eq "0" ]; then make modules if [ "$?" -eq "0" ]; then make modules_install if [ "$?" -eq "0" ]; then cp arch/i386/boot/bzImage /boot/my-new-kernel if [ "$?" -eq "0" ]; then cp System.map /boot/ if [ "$?" -eq "0" ]; then echo "Your new kernel awaits, m'lord." fi fi fi fi fi fi fi fi
... که من، شخصاً، برای پیگیری تا حدودی مشکل پیدا میکنم.
عملگرهای &&
و ||
معادلهای شل برای کنترلهای AND و OR
میباشند. اینها میتوانند به صورت فوق با هم در یک رشته زنجیر بشوند، یا به صورت:
#!/bin/sh cp /foo /bar && ( echo Success ; echo Success part II ) || ( echo Failed ; echo Failed part II )این کد یا این را منعکس میکند
Success Success part IIیا این یکی را
Failed Failed part IIنسبت به این که دستور
cp
موفق بوده یا خیر. به طور دقیق به این نگاه کنید، ساختار آن این طور استcommand && command-to-execute-on-success || command-to-execute-on-failureدر هر قسمت، فقط یک دستور میتواند باشد، میان پرانتزها
( )
یک زیرپوسته ساخته میشود، که به عنوان یک دستور منفرد توسط بالاترین سطح پوسته با آن رفتار میشود.
این شیوه برای سناریوهای موفق و ناموفق سودمند است، اما اگر میخواهید وضعیت خود دستورهای echo
را بررسی کنید، به سادگی و به سرعت در مورد اینکه کدام &&
و ||
برکدام دستور اعمال شده است، سر در گم میشوید. بنابراین این ساختار فقط برای توالی ساده فرمانها پیشنهاد میگردد.