فصل دوازدهم - آرگومانها و آپشنها
آرگومان، هر ورودی است که در هنگام اجرای یک برنامه (به هر زبانی) در خط
فرمان، به آن داده میشود. اولین آرگومان (اندیس صفر) همیشه نام/مسیر فایل
برنامه است.
یک آپشن نوعی آرگومان است که با علامت - که dash یا خط فاصله خوانده میشود، شروع میشود. اگر با یک dash شروع شود، به آن short option و اگر با دوتا شروع شود long option میگویند
مثلا h- در این مثال، یک short option است:
progname -h
و help-- یک long option است:
progname --help
در این تصویر، دستهبندی آرگومانهای خط فرمان را، به همراه یک مثال میبینید:
مثال: دستور زیر را در نظر بگیرید:
$ ls -aF fruit
در اینجا آرگومانهای دستور ls دو رشتهی aF- و fruit هستند در حالی که آپشن این دستور تنها aF- است.
برای نشان دادن استفادهی آپشنها اسکریپت زیر را نظر بگیرید که عملیات ایجاد یا خواندن یک فایل tar را برای شما انجام میدهد:
USAGE="Usage: $0 [-c|-t] [file|directory]" case "$1" in -t) TARGS="-tvf $2" ;; -c) TARGS="-cvf $2.tar $2" ;; *) echo "$USAGE" exit 0 ;; esac
حالا پس از اجرای این اسکریپت جملهی زیر نمایش داده میشود:
Usage: ./mytar [-c|-t] [file|directory]
در این اسکریپت برای ایجاد یک فایل tar از دستور زیر
$ ./mytar -c fruits
و برای خواندن آن از دستور زیر استفاده میشود
$ ./mytar -t fruits
محتویات[نهفتن] |
دستور basename
این دستور آدرس نسبی یا مطلق یک فایل را گرفته و نام آنرا نمایش میدهد. شکل کلی این دستور به صورت زیر است:
basename file
مثال:
$ basename /usr/bin/sh
که منجر به تولید خروجی زیر میشود:
sh
مثال: به طریق زیر میتوان از دستور basename در اسکریپت قبل استفاده کرد:
USAGE="Usage: `basename $0` [-c|-t] [file|directory]"
که خروجی زیر را تولید میکند:
Usage: mytar [-c|-t] [file|directory]
اشکالات رایج در کار با آرگومانها
حالا اسکریپت mytar با استفاده از آپشنها تعین میکند که کدام اسکریپت
اجرا شود. ولی مشکل دیگری که شما هنوز حل نکردهاید حالتی است که آرگومان
دوم به اسکریپت ارسال نشود. البته شما نگرانی بابت ارسال شدن آرگومان اول
ندارید زیرا در ساختار case با اسفتاده از کاراکتر * حالات ناخواسته نیز
تعریف شدهاند.
سادهترین راه برای بررسی تعداد آرگومانهای ارسال شده به برنامه استفاده
از متغیر #$ است. این متغیر تعداد آرگومان ارسال شده به برنامه را در خود
ذخیره میکند.
استفاده از این متغیر در این اسکریپت به صورت زیر میشود:
#!/bin/sh USAGE="Usage: `basename $0` [-c|-t] [file|directory]" if [ $# -lt 2 ] ; then echo "$USAGE" exit 1 fi case "$1" in -t) TARGS="-tvf $2" ;; -c) TARGS="-cvf $2.tar $2" ;; *) echo "$USAGE" exit 0 ;; esac tar $TARGS
در این اسکریپت ابتدا بررسی میشود که تعداد آرگومانهای ارسالی برابر ۲ است یا خیر.
این اسکریپت تقریبا بدون اشکال هست. اما شما همچنان میتوانید کارایی آنرا
افزایش دهید. به عنوان مثال این برنامه تنها اولین آرگومان را به عنوان یک
فایل در نظر میگیرد و همچنین بررسی نمیکند که آرگومان ارسال شده واقعا یک
فایل است یا خیر.
شما میتوانید تمام آرگومانهای ارسال شده را به وسلیهی متغیر ویژهي @#
بررسی و مورد پردازش قرار دهید یا به عبارتی دیگر میتوانید چندین فایل را
به این اسکریپت ارسال کنید. در اینصورت آپشن t- در این اسکریپت به صورت زیر
بازنویسی میشود:
case "$1" in -t) TARGS="-tvf" for i in "$@" ; do if [ -f "$i" ] ; then tar $TARGS "$i" ; fi ; done ;; -c) TARGS="-cvf $2.tar $2" ; tar $TARGS ;; *) echo "$USAGE" ; exit 0 ;; esac
تفاوت اصلی که در استفاده از آپشن t- ایجاد شده است، استفاده از یک حلقهی for برای بررسی تمام آرگومانهای ارسال شده است. در اینصورت اگر آرگومان ارسال یک فایل باشد آنگاه به یک فایل tar تبدیل میشود.
توجه: برای مشاهدهی آرگومانهای ارسال شده به یک تابع میتوان از دو متغیر ویژهی *# و @# استفاده کرد. تفاوت اصلی این دو متغیر در نحوهی تفکیک نامهایی که به عنوان آرگومان ارسال میشوند است. زمانی که شما از متغیر *# استفاده میکنید این متغیر فاصله بین نامفایلها را در نظر نمیگرد. به عنوان مثال اگر نام فایلی به صورت my tar file.tar باشد، و به عنوان یک آرگومان ارسال شود، متغیر *# نام این فایل را به عنوان سه آرگومان my و tar و file.tar در نظر میگیرد که در بسیار موارد میتواند مشکل ساز شود. در صورتی که متغیر @# نام هر فایل را به عنوان یک آرگومان در نظر میگیرد.
در اسکریپت آخر باز هم ممکن است مشکلات بسیار کوچکی وجود داشته باشد. اگر
دقیقتر به آخرین اسکریپتی که ایجاد شده نگاه کنیم، مشاهده میشود تمام
آرگومانهایی که به برنامه ارسال میشوند که شامل آرگومان اول، 1$، نیز
است، به عنوان فایل در نظر گرفته میشوند. و از آنجا که از آرگومان اول به
عنوان نحوهی انجام اسکریپت استفاده میشود نباید از این روش استفاده کرد.
در نتیجه باید آرگومان اول را از حلقهی for حذف کرد. برای اینمنظور از
دستور shift استفاده میشود.
مشکل دیگر زمانی میتواند اتفاق بیفتد که برنامه نتواند محتوای یک فایل tar
را نمایش دهد. از آنجا که متغیر ?$ کد خروج آخرین دستور را در خود
ذخیره میکند میتوان در این حالت از این متغیر برای تشخیص وقوع یا عدموقوع
خطا استفاده کرد.
با این توضیحات اسکریپت نهایی به صورت زیر پیاده سازی میشود:
#!/bin/sh USAGE="Usage: `basename $0` [-c|-t] [files|directories]" if [ $# -lt 2 ] ; then echo "$USAGE" ; exit 1 ; fi case "$1" in -t) shift ; TARGS="-tvf" ; for i in "$@" ; do if [ -f "$i" ] ; then FILES=`tar $TARGS "$i" 2>/dev/null` if [ $? -eq 0 ] ; then echo ; echo "$i" ; echo "$FILES" else echo "ERROR: $i not a tar file." fi else echo "ERROR: $i not a file." fi done ;; -c) shift ; TARGS="-cvf" ; tar $TARGS archive.tar "$@" ;; *) echo "$USAGE" exit 0 ;; esac exit $?
جداسازی آپشنها در شل
دو راه برای جدا سازی آپشنهای یک دستور در شل وجود دارد. روش اول استفاده از ساختار case است که در بخش نمونهای از آن را مشاهده کردید. روش دومی که در این بخش بررسی میشود استفاده از دستور getops است. شکل کلی این دستور به صورت زیر است:
getopts option-string variable
در این ساختار option-string رشتهای از کاراکترهای آپشنهای مختلف است
که دستور getops باید در نظر بگیرد. و variable نام متغیری است که این
آپشنها باید بر روی آن اعمال شوند.
مراحل انجام دستور getops به صورت زیر است:
- دستور getops تمام آپشنهای دستور مورد نظر را مشخص میکند. معمولا اینکار با جستجو به دنبال کاراکتر - انجام میدهد
- زمانی که آرگومانی پیدا شود که با - آغاز شود، کاراکترهای آن با کاراکترهای رشتهی option-string مقایسه میشوند
- در صورتی که تطبیقی بین کاراکترها وجود داشته باشد، متغیر variable با آن آپشن مقدار دهی میشود، به عبارت دیگر
- مراحل یک تا سه آنقدر انجام میشود تا تمام کاراکترها بررسی شوند
- زمانی که عملیات جدا سازی اتمام یافت، دستور getops یک عدد غیر صفر را برگشت میدهد که این عدد میتواند در حلقهها مورد استفاده قرار گیرد. همچنین زمانی که اجرای دستور getops پایان می یابد متغیر OPTID را با آخرین آرگومان مقدار دهی میکند
استفاده از getopts
در حال تکمیل...
#!/bin/sh USAGE="Usage: `basename $0` [-v] [-f] [filename] [-o] [filename]"; VERBOSE=false while getopts f:o:v OPTION ; do case "$OPTION" in f) INFILE="$OPTARG" ;; o) OUTFILE="$OPTARG" ;; v) VERBOSE=true ;; \?) echo "$USAGE" ; exit 1 ;; esac done shift `echo "$OPTIND - 1" | bc` if [ -z "$1" -a -z "$INFILE" ] ; then echo "ERROR: Input file was not specified." exit 1 fi if [ -z "$INFILE" ] ; then INFILE="$1" ; fi : ${OUTFILE:=${INFILE}.uu} if [ -f "$INFILE" ] ; then if [ "$VERBOSE" = "true" ] ; then echo "uuencoding $INFILE to $OUTFILE... \c" fi uuencode $INFILE $INFILE > $OUTFILE ; RET=$? if [ "$VERBOSE" = "true" ] ; then MSG="Failed" ; if [ $RET -eq 0 ] ; then MSG="Done." ; fi echo $MSG fi fi exit 0