کدهای خروج

کد‌های خروج اعدادی بین صفر و ۲۵۵ می‌باشند، که هر یک از فرمان‌های یونیکس وقتی کنترل را به پردازش والد خود میدهد، برمی‌گرداند.
اعداد دیگر می‌توانند استفاده شوند، اما با آنها نسبت به ۲۵۶ رفتار می‌شود، بنابراین ‎ 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 را بررسی کنید، به سادگی و به سرعت در مورد این‌که کدام && و || برکدام دستور اعمال شده است، سر در گم می‌شوید. بنابراین این ساختار فقط برای توالی ساده فرمان‌ها پیشنهاد می‌گردد.

Steve Parker  نوشته  Bourne و Bash راهنمای آموزشی اسکریپت نویسی
لطفاً برای بهتر دیدن صفحه از فایرفاکس استفاده کنید