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

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

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

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

پرسش و پاسخ شماره ۴۶

پرسش و پاسخ شماره ۴۶

می‌خواهم بررسی کنم که آیا یک کلمه در یک لیست وجود دارد( یا یک عنصر عضوی از یک مجموعه هست).

اگر پرسش واقعی شما «چطور می‌توانم بررسی کنم که آیا یکی از پارامترهایم ‎ -v‎ است؟» می‌باشد، پس لطفاً پرسش و پاسخ شماره 35 را ملاحظه کنید.در غیر اینصورت، خواندن را ادامه دهید....

اول از همه، بیایید اصطلاحات را مرتب کنیم. Bash مفهوم لیست‌ها یا مجموعه‌ها یا چیزی از این قبیل ندارد. Bash رشته‌ها و آرایه‌ها را دارد. رشته‌ها لیستی از کاراکترها می‌باشند، آرایه‌ها لیستی از رشته‌ها هستند.

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

در یک آرایه سنتی معین، تنها روش صحیحِ انجام این کار، اجرای یک حلقه روی تمام عناصر آرایه و کنترل آنها برای عنصر مورد جستجوی شما می‌باشد. فرض کنیم آنچه در جستجوی آن هستیم bar است و لیست ما در آرایه foo قرار دارد:

  •    # Bash
       for element in "${foo[@]}"; do
          [[ $element = $bar ]] && echo "Found $bar."
       done

اگر نیاز دارید این کار را چندین مرتبه در اسکریپت خود انجام بدهید، ممکن است بخواهید منطق آنرا در یک تابع به کار ببرید:

  •    # Bash
       isIn() {
           local pattern="$1" element
           shift
    
           for element
           do
               [[ $element = $pattern ]] && return 0
           done
    
           return 1
       }
    
       if isIn "jacob" "${names[@]}"
       then
           echo "Jacob is on the list."
       fi

یا، اگر می‌خواهید تابع شما شماره شاخص عضوی که عنصر در آن پیدا شده را بازگرداند:

  •    # Bash 3.0 or higher
       indexOf() {
           local pattern=$1
           local index list
           shift
    
           list=("$@")
           for index in "${!list[@]}"
           do
               [[ ${list[index]} = $pattern ]] && {
                   echo $index
                   return 0
               }
           done
    
           echo -1
           return 1
       }
    
       if index=$(indexOf "jacob" "${names[@]}")
       then
           echo "Jacob is the ${index}th on the list."
       else
           echo "Jacob is not on the list."
       fi

اگر لیست شما در یک رشته محصور شده، و به دلیل تقریباً ‌نابخردانه، بی پروایی نسبت به هشدارها را انتخاب کرده‌اید، می‌توانید از کُد پایین برای جستجوی کلمات در سرتاسر رشته، استفاده کنید. (تنها عذر موجه برای این مورد می‌تواند این باشد که شما در پوسته Bourne که آرایه ندارد، گیر افتاده باشید.)

  •    # Bourne
       set -f
       for element in $foo; do
          if test x"$element" = x"$bar"; then
             echo "Found $bar."
          fi
       done
       set +f

در اینجا کلمه به عنوان هر زیر رشته‌ای که با فضای سفید(یا به طور دقیق‌تر کاراکترهای فعلی در متغیر IFS) مرزبندی شده تعریف می‌شود. دستور ‎ set -f‎ از بسط glob در کلمات داخل لیست پیش‌گیری می‌کند. فعال کردن مجدد بسط glob با‎ (set +f)‎ اختیاری است.

اگر شما در حال کار با bash نسخه 4 یا ksh93 می‌باشید، به آرایه‌های انجمنی دسترسی دارید. اینها به شما اجازه می‌دهند مشکل را تجدید سازمان بدهید -- به جای ساختن لیستی از کلمات که مجاز هستند، شما می‌توانید یک آرایه انجمنی بسازید که در آن کلیدها کلماتی هستند که شما می‌خواهید در نظر بگیرید. مقادیر آنها -- بسته به طبیعت مشکل --می‌توانند با معنی باشند یا نباشند.

  •    # Bash 4
       declare -A good
       for word in "goodword1" "goodword2" ...; do
         good["$word"]=1
       done
    
       # مجاز است $foo کنترل اینکه  آیا 
       if ((${good[$foo]})); then ...

این هم یک روش دستیابی غیر موجه که شما نباید از آن استفاده کنید، اما به خاطر تکمیل مطلب ارائه می‌شود:

  •    # Bash
       if [[ " $foo " = *" $bar "* ]]; then
          echo "Found $bar."
       fi

( مشکل در اینجا آنست که فرض می‌شود space می‌تواند به عنوان جداکننده بین کلمات استفاده شود. عناصر شما ممکن است شامل فاصله باشند، که این روش را ناموفق می‌سازد!)

همان روش، برای پوسته‌های Bourne:

  •    # Bourne
       case " $foo " in
          *" $bar "*) echo "Found $bar.";;
       esac

همچنین می‌توانید از glob توسعه یافته با printf برای جستجوی کلمه در یک آرایه استفاده کنید. من این را به اندازه کافی تست نکرده‌ام، بنابراین ممکن است در بعضی موقعیت‌ها ناموفق شود --sn18

  •    # Bash
       shopt -s extglob
       # glob تبدیل آرایه به
       printf -v glob '%q|' "${array[@]}"
       glob=${glob%|}
       [[ $word = @($glob) ]] && echo "Found $word"
  • این کُد موقعی که یک عنصر آرایه شامل کاراکتر | باشد شکست می‌خورد. از اینرو، من این را به این پایین میان سایر روشها که به شیوه‌ای با محدودیت مشابه کار می‌کنند انتقال دادم. -- GreyCat

    • printf %q ‎  نیز کاراکتر | را نقل ‌قولی می‌کند، بنابراین احتمالاً اینطور نیست --sn18

grep گنو یک ویژگی ‎ \b‎ دارد که که بنا به گفته برخی بر لبه‌های کلمات مطابقت می‌کند (مرزهای کلمه). ممکن است شخصی سعی کند با کاربرد آن، رویکرد کوتاه‌تر استفاده شده فوق را بازآزمایی نماید، اما این کار توأم با خطر است:

  •    #  یکی از پارامترهای مکانی است؟ 'foo' آیا
       egrep '\bfoo\b' <<<"$@" >/dev/null && echo yes
    
       # یکی از پارامترهای مکانی است؟ '-v'  این جایی است که شکست می‌خورد: آیا
       egrep '\b-v\b' <<<"$@" >/dev/null && echo yes
       # را به صورت یک کلمه جداگانه می‌بیند "v" , \b متأسفانه ‎
       # را به چه جهنمی می‌فرستد ‎"-"‎ کسی نمی‌داند‎
    
       # هست؟ 'array' در آرایه "someword" آیا کلمه 
       egrep '\bsomeword\b' <<<"${array[@]}"
       # باشد نمی‌توانید از این استفاده کنید '-v' همان someword بدیهی است اگر کلمه

چون این ویژگی grep گنو، هم غیر قابل حمل است و هم به طور ناکافی تعریف شده، پیشنهاد می‌کنیم از آن استفاده نکنید. این مورد واقعاً در اینجا به خاطر تکمیل کردن مبحث، اشاره می‌شود.

مقایسه عمده

این شیوه تلاش می‌کند رشته مورد نظر را با تمام محتویات آرایه مقایسه نماید. به طور بالقوه می‌تواند بسیار مؤثر باشد، اما بستگی به جداکننده دارد که نباید در کمیت مورد جستجو یا در آرایه موجود باشد. در اینجا ما از ‎$'\a'‎ ، کاراکتر زنگ، استفاده کرده‌ایم چون بینهایت غیر معمول است.

  •    # usage: if has "element" list of words; then ...; fi
       has() {
         local IFS=$'\a' t="$1"
         shift
         [[ $'\a'"$*"$'\a' == *$'\a'$t$'\a'* ]]
       }

انواع برشمرده(Enumerated types)زیرنویس 1

در ksh93t یا بعد از آن شاید کسی با استفاده از دستور داخلی enum انواع، متغیرها، یا ثابتهای enum ایجاد نماید. اینها به طور مشابه با enumهای C کار می‌کنند(و خصیصه معادل در سایر زبانها). اینها می‌توانند برای محدود نمودن مقادیری که شاید به متغیری اختصاص داده شود، به کار بروند، به طوری‌که از لزوم یک بررسی سنگین در هر نوبتی که یک متغیر آرایه‌ای تنظیم یا ارجاع می‌شود، اجتناب گردد. مانند انواع تولید شده با استفاده از ‎ typeset -T‎، نتیجه یک فرمان enum، فرمان تعریف جدیدی است که می‌تواند برای معرفی اقلام آن نوع به کار برود.

# ksh93
 $ enum colors=(red green blue)
 $ colors foo=green
 $ foo=yellow
ksh: foo:  invalid value yellow‏‏
مترجم: در کُد فوق، به این دلیل خطا صادر می‌شود که ما متغیر را چنان تعریف کرده‌ایم که مقادیر قابل تخصیص به آن، به سه رنگ نامبرده محدود گردیده است

typeset -a‎ همچنین می‌تواند در ترکیب با یک نوع enum برای پذیرفتن ثابتهای enum به عنوان زیرنویس به کار برود.

# ksh93
 $ typeset -a [colors] bar
 $ bar[blue]=test1
 $ typeset -p bar
typeset -a [colors] bar=([blue]=test)
 $ bar[orange]=test
ksh: colors:  invalid value orange

برای مثالهای بیشتر ‎src/cmd/ksh93/tests/enum.sh‎ در منبع AST را ببینید.


  1. مترجم: نوع برشمرده(Enumerated type) از ویژگی‌های زبانهای برنامه نویسی پیشرفته‌ای مانند C است، که در تعریف خود لیست فراگیری از تمام مقادیر ممکن برای آن نوع متغیر را در بر دارد، مانند متغیری با نوع «روزهای هفته» که تعریف آن لیستی شامل هفت عضو با مقادیر شنبه، یکشنبه، و ... می‌باشد. (1)

CategoryShell

پرسش و پاسخ 46 (آخرین ویرایش ‎ 2012-07-24 04:36:07 ‎ توسط ormaaj)