خط فرمان لینوکس

ترجمه فارسی LinuxCommand.org

خط فرمان لینوکس

ترجمه فارسی LinuxCommand.org

خطاها و سیگنالها و Trapها- بخش 2

خطاها و سیگنالها و Trapها (ای وای!) - بخش 2

خطاها تنها طریقه‌ای نیستند که یک اسکریپت می‌تواند به طور غیرمنتظره خاتمه بیابد. شما باید به سیگنال‌ها نیز رسیدگی کنید. برنامه زیر را در نظر بگیرید:

#!/bin/bash

echo "this script will endlessly loop until you stop it"
while true; do
	: # Do nothing
done

بعد از اینکه این اسکریپت را راه‌اندازی کنید به ظاهر هنگ خواهد کرد. در واقع، مانند اکثر برنامه‌هایی که هنگ کرده به نطر می‌آیند، حقیقتاً در داخل یک حلقه گرفتار می‌شود. در این مورد، منتظر است تا فرمان true یک وضعیت خروج غیر صفر برگشت بدهد، که هرگز چنین کاری نمی‌کند. وقتی شروع شود، اسکریپت ادامه خواهد داشت تا bash سیگنالی دریافت کند که آن را متوقف خواهد نمود. شما می‌توانید چنین سیگنالی را با تایپ کردن ‎Ctrl-c‎ ارسال کنید که سیگنالی به نام SIGINT است (کوتاه شده ‎SIGnal INTerrupt‎).

پاکسازی بعدی خودتان

بسیار خوب، بنابراین یک سیگنال می‌تواند از راه برسد و اسکریپت شما را خاتمه بدهد. سیگنال چرا اهمیت دارد؟ خوب، در بسیاری از موارد اهمیت ندارد و شما می‌توانید از سیگنالها صرفنظر کنید، اما در برخی موارد اهمیت خواهد داشت.

بیایید به یک اسکریپت دیگر نگاه کنیم:

#!/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]:"‎ ظاهر می‌گردد کاربر ‎ctrl-c‎ را تایپ کند چه پیش می‌آید؟ اسکریپت در فرمان read پایان می‌پذیرد و فرمان rm هرگز اجرا نمی‌گردد. به طور واضح، ما به واکنش در برابر سیگنالهایی همچون SIGINT هنگامی که ‎ctrl-c‎ تایپ می‌شود نیاز داریم.

خوشبختانه، bash یک شیوه برای انجام فرمانها در هنگامی که سیگنالها دریافت می‌شوند ارایه می‌کند.

trap

فرمان 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 مشکل را برطرف نموده است، می‌توانیم مشاهده کنیم که محدودیت‌هایی هم دارد. به طور مهمتر از همه، تنها یک رشته منفرد شامل فرمانی که موقع دریافت سیگنال باید اجرا بشود، قبول می‌کند. شما می‌توانستید زرنگی کنید و از ‎";"‎ استفاده کرده، فرمانهای چندگانه را برای تحصیل رفتار پیچیده‌تر در رشته قرار بدهید، اما به طور صریح، ناخوشایند و زشت است. یک روش مناسب‌تر ایجاد یک تابع خواهد بود که وقتی شما می‌خواهید عملیاتی در انتهای اسکریپت خود انجام بدهید فراخوانی می‌گردد. من در اسکریپت‌هایم، به این تابع نام clean_up می‌دهم.

#!/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

تولید بدون خطر فایلهای موقت

در برنامه فوق، اقداماتی برای کمک به محفوظ داشتن فایل موقتِ مورد استفاده در اسکریپت به عمل آمده. در یونیکس استفاده از یک دایرکتوری به نام ‎/tmp‎ برای قرار دادن فایلهای موقتی مورد استفاده برنامه‌ها، یک قرارداد است. هر کسی می‌تواند فایلها را در این دایرکتوری بنویسد. این مورد به طور طبیعی به برخی نگرانی‌های امنیتی منجر می‌گردد. در صورت امکان، از نوشتن فایلها در دایرکتوری ‎/tmp‎ پرهیز کنید. شیوه ترجیح یافته، نوشتن آنها در دایرکتوری محلی از قبیل دایرکتوری ‎~/tmp‎ (یک دایرکتوری فرعی در دایرکتوری خانگی کاربر) است. در صورتیکه شما باید فایلها را در دایرکتوری ‎/tmp‎ بنویسید، باید اقداماتی برای اطمینان از غیر قابل پیش‌بینی بودن نام فایلها انجام بدهید. نام فایلهای قابل پیش‌بینی اجازه می‌دهند یک مهاجم پیوندهای نمادین به فایلهایی ایجاد کند که خود می‌خواهد شما بازنویسی کنید.

یک نام فایل خوب به شما کمک خواهد نمود معین کنید در فایل چه چیزی نوشته شده، اما به طور کامل قابل پیش‌بینی نخواهد بود. در اسکریپت فوق، سطر پایین از کد، فایل موقت ‎$TEMP_FILE‎ را ایجاد کرد:

TEMP_FILE=$TEMP_DIR/printfile.$$.$RANDOM 

متغیر ‎$TEMP_DIR‎ بر اساس دسترس‌پذیری دایرکتوری، محتوی یکی از دایرکتوری‌های ‎/tmp‎ یا ‎~/tmp‎ است. جاسازی نام برنامه در نام فایل شیوه رایجی است . ما این کار را با رشته ‎"printfile"‎ انجام داده‌ایم. سپس، از متغیر $$ پوسته برای جاسازی شماره شناسایی پردازش ‎(pid)‎ برنامه استفاده می‌کنیم. این به شناسایی آنکه کدام برنامه عهده‌دار فایل است بیشتر کمک می‌کند. به طور تعجب‌آور، شماره شناسایی پردازش به تنهایی برای ایمن کردن فایل به اندازه کافی غیرقابل پیش‌بینی نیست، بنابراین ما متغیر ‎$RANDOM‎ پوسته را برای پیوست کردن یک عدد تصادفی به نام فایل، اضافه می‌کنیم. با این شیوه یک نام فایل ایجاد می‌کنیم که هم قابل شناسایی و هم غیر قابل پیش‌بینی است.

پایان سخن

این مطلب آموزشهای LinuxCommand.org را به پایان می‌رساند. من صمیمانه امیدوارم که آنها را هم سودمند و هم خوشایند یافته باشید. اگر چنین بود، ماجراجویی خط فرمان خود را با دانلود کتاب من ادامه بدهید.