پرسش و پاسخ شماره ۳۱ - آموزش اسکریپت نویسی
X
تبلیغات
رایتل

آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

#!/bin/bash

پرسش و پاسخ شماره ۳۱

پرسش و پاسخ شماره ۳۱

تفاوت بین test و ‎[‎ و ‎ [[‎ چیست؟

[‎(فرمان test) و ‎ [[‎ (فرمان جدید test) برای ارزیابی عبارتها به کار می‌روند. ‎ [[‎ فقط در پوسته Bash و Korn کار می‌کند، و قدرتمندتر می‌باشد،‎ [‎ و test در پوسته‌های POSIX معتبر هستند. چند مثال در اینجا آمده است:

    if [ -z "$variable" ]
    then
        echo "variable is empty!"
    fi

    if [ ! -f "$filename" ]
    then
        echo "not a valid, existing file name: $filename"
    fi

و

    if [[ ! -e $file ]]
    then
        echo "directory entry does not exist: $file"
    fi

    if [[ $file0 -nt $file1 ]]
    then
        echo "file $file0 is newer than $file1"
    fi

برای کوتاهی گفتار: test ترکیب قدیمی و قابل حمل فرمان را اجرا می‌کند. تقریباً در تمام پوسته‌ها(قدیمی‌ترین پوسته‌های Bourne استثنا هستند)، ‎[‎ مترادف test است(اما به یک شناسه انتهایی‎ ] ‎ احتیاج دارد). هر چند تمام پوسته‌های مدرن دارای پیاده‌سازی داخلی ‎[‎ می‌باشند، به طور معمول بازهم یک برنامه اجرایی خارجی با آن نام وجود دارد، به عنوان مثال ‎ /bin/[‎. استاندارد POSIX یک مجموعه ویژگی الزامی برای ‎ [‎ تعریف نموده است، اما تقریباً هر پوسته‌ای ملحقاتی را با آن ارائه می‌کند. بنابراین، اگر کُد قابل حمل می‌خواهید، باید مراقب باشید که آن ملحقات را به کار نبرید.

[[‎ نگارش بهبودیافته‌ای از آن است، و یک کلمه کلیدی است، نه یک برنامه. این موضوع استفاده از آن را به طوری که در ذیل نشان داده شده آسانتر می‌سازد. ‎[[‎ توسط پوسته Korn و BASH (به عنوان مثال نگارش 2.03) شناخته می‌شود، اما توسط پوسته‌های قدیمی‌تر POSIX یا پوسته Bourne خیر.

اگر چه ‎ [‎ و ‎[[‎ اشتراک فراوانی دارند، و در عملگرهای بسیاری مانند ‎ "-f", "-s", "-n", "-z"‎ با هم سهیم می‌باشند، تفاوتهای قابل توجهی نیز وجود دارد. این هم یک لیست مقایسه‌ای:

ویژگی تست جدید [[ تست قدیمی [ مثال

مقایسه رشته‌ای

>

\> (*)

[[ a > b ]] || echo "a does not come before b"

<

\< (*)

[[ az < za ]] && echo "az comes before za"

= یا ==

=

[[ a == a ]] && echo "a equals a"

!=

!=

[[ a != b ]] && echo "a is not equal to b"

مقایسه عددی

-gt

-gt

[[ 5 -gt 10 ]] || echo "5 is not bigger than 10"

-lt

-lt

[[ 8 -lt 9 ]] && echo "8 is less than 9"

-ge

-ge

[[ 3 -ge 3 ]] && echo "3 is greater than or equal to 3"

-le

-le

[[ 3 -le 8 ]] && echo "3 is less than or equal to 8"

-eq

-eq

[[ 5 -eq 05 ]] && echo "5 equals 05"

-ne

-ne

[[ 6 -ne 20 ]] && echo "6 is not equal to 20"

ارزیابی شرطی

&&

-a (**)

[[ -n $var && -f $var ]] && echo "$var is a file"

||

-o (**)

[[ -b $var || -c $var ]] && echo "$var is a device"

گروه‌بندی عبارت

(...)

\( ... \) (**)

[[ $var = img* && ($var = *.png || $var = *.jpg) ]] &&
echo "$var starts with img and ends with .jpg or .png"

انطباق الگو

= یا ==

نا معتبر

[[ $name = a* ]] || echo "name does not start with an 'a': $name"

انطباق عبارت منظم

=~

نا معتبر

[[ $(date) =~ ^Fri\ ...\ 13 ]] && echo "It's Friday the 13th!"

(*) این یک پیوست به POSIX استاندارد است، برخی پوسته‌ها شاید آن را داشته باشند و برخی نداشته باشند.

(**) عملگرهای ‎ -a‎ و ‎-o‎، و ‎ گروه‌بندی ( ... )‎، در POSIX تعریف شده‌اند اما فقط برای وضعیت های شدیداً محدود. استفاده از اینها تشویق نمی‌شود، شما باید به جای آن از چندین ‎ [‎ استفاده کنید:

  • if [ "$a" = a ] && [ "$b" = b ]; then ...

  • if { [ "$a" = a ] || [ "$b" = b ] ; } && [ "$c" = c ]; then ...

گزینه‌های اولیه‌ای که در ‎[[‎ تعریف شده، اما شاید ‎ [‎ فاقد آن باشد(بسته به پیاده‌سازی):

شرح

گزینه‌ها

مثال

ورودی(فایل یا شاخه) موجود است

-e

[[ -e $config ]] && echo "config file exists: $config"

فایل جدیدتر یا قدیمی‌تر از دیگری است

-nt یا -ot

[[ $file0 -nt $file1 ]] && echo "$file0 is newer than $file1"

هر دو فایل یکسان هستند

-ef

[[ $input -ef $output ]] && { echo "will not overwrite input file: $input"; exit 1; }

نفی کردن

!

[[ ! -u $file ]] && echo "$file is not a setuid file"

لیکن تفاوتهای ظریف بیشتری وجود دارد.

  • تفکیک کلمه یا بسط glob بواسطه ‎[[‎ انجام نخواهد شد(و بنابراین بسیاری از شناسه‌ها نیازی به نقل‌قولی شدن ندارند):

     file="file name"
     [[ -f $file ]] && echo "$file is a file"

    با وجود اینکه ‎ $file‎ نقل‌قولی نشده و شامل فضای سفید می‌باشد، کار می‌کند. با ‎ [‎ لازم است متغیر نقل‌قولی بشود:

     file="file name"
     [ -f "$file" ] && echo "$file is a file"

    این امر استفاده از ‎ [[‎ را آسانتر و استعداد خطا را کمتر می‌کند.

  • در ‎ [[‎ نیازی به پوشش دادن(با کاراکتر گریز) پرانتزها نیست:

     [[ -f $file1 && ( -d $dir1 || -d $dir2) ]]
     [ -f "$file1" -a \( -d "$dir1" -o -d "$dir2" \) ]
  • از bash نگارش 4.1 مقایسه رشته با استفاده از ‎ <‎ یا ‎>‎ وقتی در ‎[[‎ انجام می‌شود منطقه جاری لحاظ می‌شود، اما در ‎[‎ یا test لحاظ نمی‌شود. در حقیقت، ‎[‎ و test، حتی اگر در صفحات man گفته باشد انجام می‌دهد، هرگز مرتب‌سازی منطقه‌ای به کار نبرده‌اند. در نگارشهای Bash قبل از 4.1 نیز ‎[[‎ مرتب‌سازی نسبت به منطقه به کار نمی‌برد.

به عنوان یک قاعده کلی، ‎ [[‎ برای رشته‌ها و فایلها به کار می‌رود. اگر می‌خواهید اعداد را مقایسه کنید، از یک عبارت محاسباتی استفاده کنید، به عنوان نمونه

# Bash
i=0
while ((i<10)); do ...

چه وقت باید فرمان تست جدید ‎ [[‎ به کار برود، و چه موقع فرمان قدیمی ‎ [‎؟ اگر قابلیت حمل به BourneShell اهمیت دارد، باید ترکیب قدیمی استفاده گردد. از طرف دیگر ، اگر اسکریپت به BASH یا KornShell احتیاج دارد، ترکیب جدید خیلی بیشتر قابل انعطاف است.

فصل شرط ها و بررسی‌ها در راهنمای Bash را ببینید.

اصول نظری

نظریه پشت تمام این مطالب آنست که ‎ [‎ فرمان ساده‌ایست، در جاییکه ‎[[‎ فرمانی مرکب است. ‎[‎ شناسه‌هایش را مانند هر فرمان دیگری دریافت می‌کند، اما اکثر فرمانهای مرکب زمینه تجزیه خاصی را ارائه می‌کنند که قبل از هرگونه پردازش دیگری انجام می‌شود. به طورنوعی این مرحله کلمات رزرو شده یا عملگرهای کنترلی مخصوص هر فرمان مرکب که آن را به قسمتهایی تجزیه می‌کند یا بر روند کنترل اثر می‌گذارد را جستجو می‌کند. عملگرهای and و or بررسی منطقی عبارت می‌توانند دور زده شوند، زیرا در این روش آنها خاص هستند(به عنوان مثال مانند ;;، elif، و else هستند). بر خلاف عبارت حسابی، که به طور معمول در آن تمام بسط‌ها از چپ به راست، با رشته نتیجه‌ای که به عنوان موضوع محاسباتی تفسیر می‌شود، انجام می‌گردند.

  • فرمان مرکب محاسباتی عملگرهای ویژه‌ای ندارد. فقط یک متن ارزیابی، یعنی یک عبارت محاسباتی منفرد دارد. عبارتهای محاسباتی نیز دارای عملگر ها هستند، که بعضی از آنها در ضمن مرحله ارزیابی حسابی(که آخر از همه رخ می‌دهد) بر روند کنترل اثر می‌گذارند .
     # Bash
     (( 1 + 1 == 2 ? 1 : $(echo ‎"‏آنچه شما فکر می‌کنید را انجام نمی‌دهد‏"‎ >&2; echo 1) ))
  • از طرف دیگر، عبارتهای تست، عملگرها را به عنوان بخشی از ترکیب دستوری خودشان دارند، که در انتهای دیگر طیف قرار می‌گیرند (اول ارزیابی شده).
     # Bash
     [[ '1 + 1' -eq 2 && $(echo ‎"‏اما این احتمالاً آنچه را انتظار دارید انجام می‌دهد‏‎" >&2) ]]
  • تست های سَبکِ قدیم راهی برای کنترل ارزیابی ندارند، زیرا شناسه‌های آنها ویژه نیستند.
        # Bash
        [ $((1 + 1)) -eq 2 -o $(echo 'No short-circuit' >&2) ]
  • احتمالاً قبل از انجام بسط ها، با جستجوی نشانه‌های ویژه فرمان مرکب، مدیریت خطای متفاوتی امکان پذیر می‌شود. ‎[[‎ می‌تواند حضور بسط‌هایی که یک کلمه نتیجه نمی‌دهند ولی بازهم اگر مشخص نشوند یک خطا می‌سازند را تشخیص بدهد. دستورات معمولی نمی‌توانند.

     # Bash 
     ( set -- $(echo 'بسط‌های غیر نقل‌قولی تهی پارامترهای تهی را نتیجه نمی‌دهند'‎ >&2); echo $# )
     [[ -z $(:) ]] && echo ' به عنوان یک شناسه فراهم شده و تهی ارزیابی نمی‌شود-z ‎'
     [ -z ] && echo ' به عنوان یک شناسه فراهم نشده و خطایی گزارش نمی‌کند-z‎'
      #را مجبور به تعیین یک شناسه نماید نیست Bash در اینجا راهی برای آنکه بتواند
     [[ -z ]]  #.این موجب یک خطا خواهد شد که دستورات معمولی نمی‌توانند تشخیص بدهند
  • به دلیل بسیار مشابهی، زیرا عملگرهای ‎ [‎ درست مثل شناسه‌ها هستند، برخلاف ‎ [[‎، شما می‌توانید برای فرمان معمولیtest عملگرها را به عنوان پارامترها تعیین نمایید. ممکن است این یک محدودیت برای ‎ [[‎ به نظر برسد، اما تقریباً همیشه بخش زیرین نسبتاً می‌چربد.

      # ksh93
    
      args=('0' '-gt' '1')
     
      (( $(print '0 > 1') )) # دستور معتبر، مطابق انتظار،  وضعیت خروج ۱ است 
      [ "${args[@]}" ]       #   این هم با ۱ خارج می‌شود
      [[ ${args[@]} ]]       # دستور معتبر، اما گمراه کننده. وضعیت خروج صفر است‎
      # می‌باشد [[ -n '0 -gt 1 ]] آشکار می‌کند که فرمان حاصل از بسط، set -x دستور 
  • به خاطر داشته باشید که کدام عملگرها متعلق به کدام ساختارهای پوسته هستند. ترتیب بسط ها می‌تواند باعث نتایج شگفت‌انگیز گردد، مخصوصاً موقع اختلاط و تودرتویی زمینه‌های متفاوت ارزیابی!
    # ksh93
    typeset -i x=0
        
    ( print "$(( ++x, ${ x+=1; print $x >&2;}1, x ))"      ) #  را چاپ می‌کند‎ 1,2
    ( print "$(( $((++x)), ${ x+=1; print $x >&2;}1, x ))" ) # را چاپ می‌کند ‎ 2,2 
                                               # به دلیل آنکه اول بسط انجام می‌شود 


CategoryShell

پرسش و پاسخ 31 (آخرین ویرایش ‎ 2012-05-27 19:22:57 ‎ توسط cpc18-rdng20-2-0-cust501)