خطاها تنها طریقهای نیستند که یک اسکریپت میتواند به طور غیرمنتظره خاتمه بیابد. شما باید به سیگنالها نیز رسیدگی کنید. برنامه زیر را در نظر بگیرید:
#!/bin/bash echo "this script will endlessly loop until you stop it" while true; do : # Do nothing done
بعد از اینکه این اسکریپت را راهاندازی کنید به ظاهر هنگ خواهد کرد. در واقع، مانند اکثر برنامههایی که هنگ کرده به نطر میآیند، حقیقتاً در داخل یک حلقه گرفتار میشود. در این مورد، منتظر است تا فرمان true یک وضعیت خروج غیر صفر برگشت بدهد، که هرگز چنین کاری نمیکند. وقتی شروع شود، اسکریپت ادامه خواهد داشت تا bash سیگنالی دریافت کند که آن را متوقف خواهد نمود. شما میتوانید چنین سیگنالی را با تایپ کردن
بسیار خوب، بنابراین یک سیگنال میتواند از راه برسد و اسکریپت شما را خاتمه بدهد. سیگنال چرا اهمیت دارد؟ خوب، در بسیاری از موارد اهمیت ندارد و شما میتوانید از سیگنالها صرفنظر کنید، اما در برخی موارد اهمیت خواهد داشت.
بیایید به یک اسکریپت دیگر نگاه کنیم:
#!/bin/bash # برنامهای برای چاپ یک فایل متن با سرایندها و پانویسها TEMP_FILE=/tmp/printfile.txt pr $1 > $TEMP_FILE echo -n "Print file? [y/n]: " read if [ "$REPLY" = "y" ]; then lpr $TEMP_FILE fi
این اسکریپت یک فایل متن تعیین شده در سطر فرمان را با فرمان pr پردازش میکند و نتیجه را در یک فایل موقت ذخیره میکند. بعداُ، از کاربر سوال میکند که آیا میخواهد فایل چاپ شود. اگر کاربر "y" را تایپ نماید، آنوقت فایل موقت را برای چاپ به برنامه lpr عبور میدهد. (اگر شما در عمل دارای چاپگر متصل به سیستم خود نیستید میتوانید less را جایگزین lpr نمایید.)
اکنون، من قبول دارم که این اسکریپت دارای مشکلات طراحی بسیار است. در حالیکه به یک نام فایل ارایه شده در سطر فرمان احتیاج دارد، بررسی نمیشود که آیا یک نام ارایه شده، و بررسی نمیکند که فایل در واقع موجود است. اما مشکلی که من میخواهم بر آن تمرکز نمایم این حقیقت است که وقتی اسکریپت خاتمه مییابد، فایلِ موقت را پشت سرش باقی میگذارد.
شیوه مناسب، حکم کردن به آن خواهد بود که وقتی اسکریپت خاتمه مییابد فایل $TEMP_FILE حذف بشود. این کار به آسانی با افزودن کد زیر به انتهای اسکریپت انجام میشود:
rm $TEMP_FILE
به نظر میرسد با این کد مشکل برطرف خواهد شد، اما اگر موقعی که اعلان "Print file? [y/n]:" ظاهر میگردد کاربر
خوشبختانه، bash یک شیوه برای انجام فرمانها در هنگامی که سیگنالها دریافت میشوند ارایه میکند.
فرمان trap اجرای یک فرمان در موقعی که یک سیگنال توسط اسکریپت دریافت میشود را میسر میسازد. این فرمان به این شکل کار میکند:
trap arg signals
"signals" یک فهرست از سیگنالها برای گوش سپردن به آنها و "arg" یک فرمان است برای آنکه وقتی یکی از سیگنالها دریافت میشود اجرا گردد. برای اسکریپت چاپ خود، ما میتوانیم مشکل سیگنال را به این طریق اداره کنیم:
#!/bin/bash # برنامهای برای چاپ یک فایل متن با سرایندها و پانویسها TEMP_FILE=/tmp/printfile.txt trap "rm $TEMP_FILE; exit" SIGHUP SIGINT SIGTERM pr $1 > $TEMP_FILE echo -n "Print file? [y/n]: " read if [ "$REPLY" = "y" ]; then lpr $TEMP_FILE fi rm $TEMP_FILE
در اینجا ما یک فرمان trap اضافه کردیم که اگر هر کدام از سیگنالهای لیست شده دریافت بشود فرمان "rm $TEMP_FILE" را اجرا خواهد نمود. سه سیگنال لیست شده، رایجترین مواردی هستند که شما مواجه خواهید شد، اما تعداد بسیار بیشتری وجود دارد که میتوانند تعیین بشوند. برای فهرست کامل، فرمان "trap -l" را تایپ کنید. علاوه بر فهرست کردن سیگنالها به وسیله نام آنها، ممکن است به طور جایگزین آنها را با شماره آنها مشخص کنید.
در حالیکه فرمان trap مشکل را برطرف نموده است، میتوانیم مشاهده کنیم که محدودیتهایی هم دارد. به طور مهمتر از همه، تنها یک رشته منفرد شامل فرمانی که موقع دریافت سیگنال باید اجرا بشود، قبول میکند. شما میتوانستید زرنگی کنید و از "
#!/bin/bash # برنامهای برای چاپ یک فایل متن با سرایندها و پانویسها TEMP_FILE=/tmp/printfile.txt clean_up() { # انجام برنامه خانهتکانی خروج rm $TEMP_FILE exit } trap clean_up SIGHUP SIGINT SIGTERM pr $1 > $TEMP_FILE echo -n "Print file? [y/n]: " read if [ "$REPLY" = "y" ]; then lpr $TEMP_FILE fi clean_up
استفاده از یک تابع پاکسازی ایده خوبی برای روالهای مدیریت خطای شما نیز هست. در هر صورت، وقتی برنامه شما خاتمه مییابد (به هر دلیل)، شما باید خودتان بعداً پاکسازی کنید. در اینجا نگارش تکمیل شده برنامه ما با مدیریت خطا و سیگنال بهبودیافته:
#!/bin/bash # برنامهای برای چاپ یک فایل متن با سرایندها و پانویسها # Usage: printfile file # محلی کاربر تنظیم میشود tmp ایجاد یک نام فایل موقتی که به دایرکتوری # .و دارای یک نام است که در برابر حملات به فایلهای موقت مقاوم است if [ -d "~/tmp" ]; then TEMP_DIR=~/tmp else TEMP_DIR=/tmp fi TEMP_FILE=$TEMP_DIR/printfile.$$.$RANDOM PROGNAME=$(basename $0) usage() { # نمایش پیغام نحوه کاربرد در خروجی استاندارد خطا echo "Usage: $PROGNAME file" 1>&2 } clean_up() { # انجام برنامه خانهتکانی خروج # به طور اختیاری یک وضعیت خروج قبول میکند rm -f $TEMP_FILE exit $1 } error_exit() { # نمایش پیغام خطا و خروج echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2 clean_up 1 } trap clean_up SIGHUP SIGINT SIGTERM if [ $# != "1" ]; then usage error_exit "one file to print must be specified" fi if [ ! -f "$1" ]; then error_exit "file $1 cannot be read" fi pr $1 > $TEMP_FILE || error_exit "cannot format file" echo -n "Print file? [y/n]: " read if [ "$REPLY" = "y" ]; then lpr $TEMP_FILE || error_exit "cannot print file" fi clean_up
در برنامه فوق، اقداماتی برای کمک به محفوظ داشتن فایل موقتِ مورد استفاده در اسکریپت به عمل آمده. در یونیکس استفاده از یک دایرکتوری به نام
یک نام فایل خوب به شما کمک خواهد نمود معین کنید در فایل چه چیزی نوشته شده، اما به طور کامل قابل پیشبینی نخواهد بود. در اسکریپت فوق، سطر پایین از کد، فایل موقت $TEMP_FILE را ایجاد کرد:
TEMP_FILE=$TEMP_DIR/printfile.$$.$RANDOM
متغیر $TEMP_DIR بر اساس دسترسپذیری دایرکتوری، محتوی یکی از دایرکتوریهای
این مطلب آموزشهای