خوب، تا اندازه بسیاری بستگی به آن دارد که چه کاری میخواهید با آنها انجام بدهید. چندین راهکار ، هرکدام با ضعف و قوتهای مربوط به خود، وجود دارد.
فهرست مطالب
این راهکار هر مجموعهای از گزینههای اختیاری را مدیریت میکند، زیرا تجزیه کننده را خود شما مینویسید. برای 90% برنامهها، سادهترین رویکرد است(به دلیل آنکه شما به ندرت به موارد تفننی نیاز دارید).
این مثال ترکیبی از گزینههای کوتاه و بلند را مدیریت خواهد کرد. توجه کنید چگونه هر دو شکل "--file" و "--file=FILE" اداره میشوند.
1 #!/bin/sh
2 # POSIX ترکیب دستوری پوسته
3
4 # تنظیم دوباره تمام متغیرهایی که باید برقرار شوند
5 file=""
6 verbose=0
7
8 while :
9 do
10 case $1 in
11 -h | --help | -\?)
12 # شما usage()یا Help() فراخوانی تابع
13 exit 0 # را به کارنبرید exit 1 این یک خطا نیست، کاربر کمک خواسته
14 ;;
15 -f | --file)
16 file=$2 # را گرفتهاید FILE میتوانید بررسی کنید که واقعاً
17 shift 2
18 ;;
19 --file=*)
20 file=${1#*=} # "=" حذف هر چیز تا رسیدن به
21 shift
22 ;;
23 -v | --verbose)
24 # یکی به درازنویسی اضافه میکند -v هر نمونه از
25 verbose=$((verbose+1))
26 shift
27 ;;
28 --) # پایان تمام گزینه ها
29 shift
30 break
31 ;;
32 -*)
33 echo "WARN: Unknown option (ignored): $1" >&2
34 shift
35 ;;
36 *) # while گزینه دیگری موجود نیست، توقف حلقه
37 break
38 ;;
39 esac
40 done
41
42 # .به فرض که گزینههایی لازم باشند، کنترل آنکه آنها را گرفتهایم
43
44 if [ ! "$file" ]; then
45 echo "ERROR: option '--file FILE' not given. See --help" >&2
46 exit 1
47 fi
48
49 # .بقیه برنامه در اینجا
50 # اگر پس از گزینهها، (به عنوان مثال) فایلهای ورودی موجود باشند، آنها
51 # .باقی خواهند ماند"$@" در پارامترهای مکانی
52
این تجزیه کننده گزینههای جداگانهای که به یکدیگر الحاق گردیدهاند را مدیریت نمیکند(مانند -xvf که به عنوان -x -v -f قبول بشود). این مورد با تلاش میتوانست افزوده شود، اما به عنوان یک تمرین برای خواننده واگذار گردید.
برخی برنامه نویسان Bash دوست دارند، برای هشیاری در برابر متغیرهای استفاده نشده، این کُد را در ابتدای اسکریپتهای خودشان بنویسند:
set -u # set -o nounset یا
استفاده از این دستور حلقه فوق را ناموفق میکند، چون "$1" شاید درموقع ورود به حلقه برقرار نباشد. چهار راه حل برای این موضوع وجود دارد:
عدم استفاده از -u.
تعویض case $1 in با case ${1+$1} in (تمام کُدهای پس از آن را که set -u نقض میکند، به خوبی توانمند میسازد).
تعویض case $1 in با case ${1-} in (هر متغیری که امکان اعلان نشدن دارد، برای ممانعت از اثر set -u، میتواند به صورت ${variable-} نوشته بشود).
هرگز از getopt(1) استفاده نکنید. getopt نمیتواند شناسههای رشتهای تهی، یا شناسههای دارای فضای سفید را اداره کند. لطفاً اصلاً وجود آن را فراموش کنید.
پوسته POSIX (و سایرین) به جای آن getopts را ارائه مینمایند که برای استفاده مطمئن است. این هم یک مثال ساده getopts:
1 #!/bin/sh
2
3 # POSIX یک متغیر
4 OPTIND=1 # قبلاً در پوسته به کار رفته باشد getopts تنظیم مجدد در حالتی که
5
6 # :ارزش گذاری اولیه متغیرهای خودمان
7 output_file=""
8 verbose=0
9
10 while getopts "h?vf:" opt; do
11 case "$opt" in
12 h|\?)
13 show_help
14 exit 0
15 ;;
16 v) verbose=1
17 ;;
18 f) output_file=$OPTARG
19 ;;
20 esac
21 done
22
23 shift $((OPTIND-1))
24
25 [ "$1" = "--" ] && shift
26
27 echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
28
29 # انتهای فایل
30
مزایای getopts عبارتند از:
مواردی مانند -vf filename که روش یونیکسی مورد انتظار میباشد را به طور خودکار مدیریت میکند.
اشکال getopts آن است که فقط گزینههای کوتاه را مدیریت میکند(-h، نه --help).
یک آموزش getopts وجود دارد که شرح میدهد هر یک از ترکیبات دستوری و متغیرها به چه معنی هستند. در bash، همچنین help getopts نیز وجود دارد، که میتواند آموزنده باشد.
همچنین بازهم در فراخوانی getopts آن اشکال وجود دارد که گزینهها در حداقل دو، یا شاید سه مکان کُد میشوند، در جمله case که آنها را پردازش میکند و احتمالاً درپیغام راهنمایی که یکی از این روزها خواهان نوشتن آن میشوید. این یک فرصت کلاسیک برای رسوخ خطاها در کُدهای نوشته و نگهداری شده -غالباً هنوز زیاد توسعه نیافته، یا خیلی قدیمیتر- است. این مورد با استفاده از توابع فراخوانی برگشتی، میتواند پیشگیری بشود، اما این رویکرد نوعی خنثی نمودن هدف استفاده از getopts میباشد.
این هم یک مثال که مدعی تجزیه گزینههای بلند با getopts میباشد. ایده اصلی کاملاً ساده است: فقط "-:" را در optstring قرار بدهید. این ترفند به پوستهای نیاز دارد که گزینه-شناسه را اجازه دهد(یعنی نام فایل در "-f filename" به صورت "-ffilename" به گزینه الحاق بشود). استاندارد POSIX میگوید یک فاصله باید بین آنها باشد، bash و dash نوع "-ffilename" را اجازه میدهند، اما اگر کسی میخواهد اسکریپت قابل حمل بنویسد، نباید به این ارفاق تکیه کند.
1 #!/bin/bash
2 # .استفاده میکند. به طوری که نوشته شده قابل حمل نیست bash از ملحقات
3
4 optspec=":h-:"
5
6 while getopts "$optspec" optchar
7 do
8 case "${optchar}" in
9 -)
10 case "${OPTARG}" in
11 loglevel)
12 eval val="\$${OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
13 echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2
14 ;;
15 loglevel=*)
16 val=${OPTARG#*=}
17 opt=${OPTARG%=$val}
18 echo "Parsing option: '--${opt}', value: '${val}'" >&2
19 ;;
20 esac
21 ;;
22 h)
23 echo "usage: $0 [--loglevel[=]<value>]" >&2
24 exit 2
25 ;;
26 esac
27 done
28
29 # End of file
30
در عمل، این مثال به قدری ابهام آلود است، که اگر این تنها دلیل استفاده از getopts باشد، شاید افزودن پشتیبانی از گزینه الحاق شده به حلقه تجزیه دستی (مانند -vf filename)، نسبت به آن قابل ترجیح باشد.
در اینجا نگارش بهبود یافته و عمومیتری از تلاش فوق برای افزودن، پشتیبانی ازگزینههای بلند هنگام استفاده از getopts، آوردهایم:
1 #!/bin/bash
2 # .استفاده میکند. به صورت نوشته شده قابل حمل نیست bash از ملحقات
3
4 declare -A longoptspec
5 longoptspec=( [loglevel]=1 ) # استفاده از آرایه انجمنی برای تعیین آنکه
# longlevelگزینه بلند چند شناسه را قبول میکند، در این حالت تعریف میکنیم که
#یک شناسه دارد یامیپذیرد، گزینههای بلندی که به این طریق لیست نشدهاند به طور
# پیش فرض تعداد شناسه آنها صفر است
6 optspec=":h-:"
7 while getopts "$optspec" opt; do
8 while true; do
9 case "${opt}" in
10 -) # میباشد value= نام گزینه بلند یا نام گزینه بلند OPTARG
11 if [[ "${OPTARG}" =~ .*=.* ]]
#فقط یک شناسه امکان پذیر است --key=value با این شکل
12 then
13 opt=${OPTARG/=*/}
14 OPTARG=${OPTARG#*=}
15 ((OPTIND--))
16 else
# شناسههای چندتایی امکان پذیر است --key value1 value2 با ساختار
17 opt="$OPTARG"
18 OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
19 fi
20 ((OPTIND+=longoptspec[$opt]))
21 continue # تنظیم گردیدند میتوانیم همان طور opt و OPTARG اکنون که
# گرفته بودیم، آنها را پردازش نماییم getopts که اگر شناسه بلند را از
22 ;;
23 loglevel)
24 loglevel=$OPTARG
25 ;;
26 h|help)
27 echo "usage: $0 [--loglevel[=]<value>]" >&2
28 exit 2
29 ;;
30 esac
31 break; done
32 done
33
34 # End of file
35
با این نسخه میتوانید گزینههای کوتاه و بلند را در کنار هم داشته باشید ونیازی به ویرایش کُد از سطر 10 تا 22 نخواهید داشت. این راه حل همچنین میتواند شناسههای چندگانه برای گزینههای بلند را اداره کند، فقط از ${OPTARG} یا ${OPTARG[0]} برای اولین شناسه استفاده کنید، و از ${OPTARG[1]} برای دومین، ${OPTARG[2]} برای سومین شناسه و به همین ترتیب. این مورد نیز همان کمبود نمونه ماقبل خود را دارد و قابل حمل نبوده، مختص bash است.
یک رویکرد دیگر کنترل گزینهها در دستورات if میباشد. تابعی مانند این یکی شاید مفید باشد:
1 #!/bin/bash
2
3 HaveOpt ()
4 {
5 local needle=$1
6 shift
7
8 while [[ $1 == -* ]]
9 do
10 # به معنی پایان گزینهها میباشد "--" مطابق قرارداد
11 case "$1" in
12 --) return 1 ;;
13 $needle) return 0 ;;
14 esac
15
16 shift
17 done
18
19 return 1
20 }
21
22 HaveOpt --quick "$@" && echo "Option quick is set"
23
24 # End of file
25
و این در صورتی کار میکند که اسکریپت به این شکل اجرا بشود:
اما بدون "-" یا با "--" در اولین شناسه متوقف میشود:
البته، این رویکرد(تکرار روی لیست شناسه در هر نوبتی که میخواهید یک مورد را کنترل کنید،) نسبت به فقط یکبار تکرار و تنظیم نشانه متغیرها، خیلی کمتر کارامد است.
همچنین گزینهها را در سرتاسر برنامه پخش میکند. گزینه لفظی --quick ممکن است، دور از هر نام گزینه دیگر، در صدها سطر از بدنه اصلی برنامه ظاهر شود. این کابوسی برای حفظ ونگهداری است.
bhepple استفاده از process-getopt ( با مجوز GPL ) را پیشنهاد میکند و این نمونه کد را ارائه مینماید:
PROG=$(basename $0) VERSION='1.2' USAGE="A tiny example using process-getopt(1)" # برای تعریف تعدادی گزینه process-getopt فراخوانی توابع source process-getopt SLOT="" SLOT_func() { [ "${1:-""}" ] && SLOT="yes"; } # SLOTفراخوان برگشتی برای گزینه add_opt SLOT "boolean option" s "" slot TOKEN="" TOKEN_func() { [ "${1:-""}" ] && TOKEN="$2"; } # TOKEN فراخوانی برگشتی گزینه add_opt TOKEN "this option takes a value" t n token number add_std_opts # و غیره --help تعریف گزینههای استاندارد TEMP=$(call_getopt "$@") || exit 1 eval set -- "$TEMP" # getopt(1)درست مثل # حذف گزینه ها از سطر دستور process_opts "$@" || shift "$?" echo "SLOT=$SLOT" echo "TOKEN=$TOKEN" echo "args=$@"
اینجا برای بسیار آسانتر نمودن تألیف و نگهداری، تمام اطلاعات در مورد هر گزینه فقط در یک محل تعریف میشود.مقدار زیادی از چرکینی کار به طور خودکار زدوده میشود و از استانداردهای getopt(1) متابعت مینماید، زیرا getopt را برای شما فراخوانی میکند.
در حقیقت، آنچه نویسنده غفلت کرده که بگوید، آن است که به جای getopt، واقعاً از معناشناسی getopts استفاده میشود. من این تست را اجرا کردم:
~/process-getopt-1.6$ set -- one 'rm -rf /' 'foo;bar' "'" ~/process-getopt-1.6$ call_getopt "$@" -- 'rm -rf /' 'foo;bar' ''\'''
به نظر میرسد به اندازه کافی برای اداره گزینههای تهی، گزینههای شامل فضای سفید، و گزینههای شامل نقلقول منفرد، هوشمند باشد، به نحوی که باعث میگرددeval پیش روی شما متوقف نشود. اما این ظهرنویسی برای نرمافزار process-getopt به طور کلی نیست، من به اندازه کافی در بارهاش نمیدانم. -GreyCat
این برنامه نوشته شده و تست شده در لینوکس است، جایی که getopt(1) از گزینههای بلند پشتیبانی میکند. برای قابلیت انتقال، در حین اجرا getopt(1) محلی را تست میکند و اگریک نمونه غیر-گنو پیدا کند(یعنی نمونهای که برای getopt --test عدد 4 برنمیگرداند) فقط گزینههای کوتاه را پردازش میکند. از دستور داخلی bash به نام getopts(1) استفاده نمیکند. -bhepple
پرسش و پاسخ 35 (آخرین ویرایش 2012-10-22 20:41:23 توسط GreyCat)