آموزش‌های خط فرمانی

این وبلاگ تلاش می‌کند گامی در حد بضاعت در جهت آموزش خط فرمان و اسکریپت‌نویسی پوسته گنو-لینوکس بردارد.

آموزش‌های خط فرمانی

این وبلاگ تلاش می‌کند گامی در حد بضاعت در جهت آموزش خط فرمان و اسکریپت‌نویسی پوسته گنو-لینوکس بردارد.

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

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

غیر مستقیم

اندیشیدن قبل از به کاربردن غیرمستقیم

قرار دادن نام متغیرها یا هر ترکیب دستوری bash درون پارامترهای دیگر به طور کلی ایده نامساعدی است. این کار از جدایی بین کُد و داده تخطی می‌کند، و بدین ترتیب شما را در شیب لغزنده‌ای به طرف باگها، مسائل امنیت، و غیره قرار می‌دهد. حتی وقتی می‌دانید که «کاملاً می‌فهمید»، زیرا شما به «طور دقیق می‌دانید و می‌فهمید که چه کار می‌کنید»، باگها برای همگی ما پیشامد می‌کنند و سزاوار است رعایت جدایی را تمرین کنیم تا دامنه زیانهایی را که می‌توانند موجب گردند، به حداقل برسانیم.

گذشته از آن، کُد شما را نیز نامفهوم و غیر شفاف می‌سازد.

به طور طبیعی، در اسکریپت‌نویسی bash، شما ابداً نیازی به ارجاع‌های غیر مستقیم ندارید. عموماً، اشخاص زمانی به این راه حل متوجه می‌شوند که در مورد آرایه‌های Bash نمی‌دانند یا درک نمی‌کنند، یا سایر ویژگی‌های Bash از قبیل توابع را به طور کامل ملاحظه نکرده‌اند.

ارزیابی متغیرهای غیرمستقیم/مرجع‌ها

BASH به شما اجازه می‌دهد پارامتر را به طور غیرمستقیم بسط دهید--یعنی،یک متغیر ممکن است شامل نام متغیر دیگری باشد:

  •  # Bash
     realvariable=contents
     ref=realvariable
     echo "${!ref}"   # محتویات متغیر اصلی را چاپ می‌کند

(ksh93) پوسته Korn تفاوت کلی دارد، ترکیب دستوری قدرتمندتر-- دستور nameref (همچنین به عنوان ‎typeset -n‎ نیز شناخته می‌شود):

  •  # ksh93
     realvariable=contents
     nameref ref=realvariable
     echo "$ref"      #  محتویات متغیر اصلی را چاپ می‌کند

متأسفانه، برای پوسته‌های غیر از Bash و ksh93 هیچ ترکیب دستوری برای ارزیابی متغیر مرجع شده، موجود نیست. شما باید از eval استفاده کنید، که یعنی شما، جهت اجتناب از عاقبت آن، باید متحمل اقدامات بسیار زیادی برای سترون سازی داده‌های خود بشوید.

تصور یک استفاده عملی از این، که به همان آسانی کاربرد آرایه انجمنی ، انجام بشود مشکل است. اما اشخاص دائماً آنرا می‌پرسند(این به طور مؤثق سؤال مکرراً پرسیده شده‌ای می‌باشد).

فرمان nameref پوسته ksh93 به ما اجازه می‌دهد با ارجاع‌ها به آرایه‌ها به همان خوبی متغیرهای معمولی کار کنیم. برای مثال:

  •  # ksh93
     myfunc() {
       nameref ref=$1
       echo "array $1 has ${#ref[*]} elements"
     }
     realarray=(...)
     myfunc realarray

ما از هیچ ترفندی که بتواند رونوشتی از آن توانایی در پوسته‌های POSIX یا Bourne ایجاد کند آگاه نمی‌باشیم(غیر از کاربرد eval، که اجرای آن به طور امن به شدت دشوار است). Bash تقریباً می‌تواند آنرا انجام بدهد -- برخی ترفندهای آرایه غیرمستقیم کار می‌کنند، و برخی دیگر خیر، و ما نمی‌دانیم که آیا ترکیب مورد بحث در انتشارهای آینده پایدار باقی می‌ماند. پس این هک را با مسؤلیت خودتان استفاده کنید.

  •  # Bash -- trick #1.  به نظر می‌رسد در نگارش ۲ و بالاتر کار می‌کند
     realarray=(...) ref=realarray; index=2
     tmp="$ref[$index]"
     echo "${!tmp}"                    # عضو آرایه با شاخص ۲ را می‌دهد
    
     # Bash -- trick #2.  ظاهراً در نگارش ۳ و بالاتر کار می‌کند‎
     #  کار نمی‌کند bash 2.05b در نگارش  
     tmp="$ref[@]"
     printf "<%s> " "${!tmp}"; echo    # .در تمام طول آرایه تکرار می‌شود

بازیابی شاخص‌های آرایه به طور مستقیم با استفاده از بسط غیر مستقیم‎ ${!var}‎ پوسته Bash امکان پذیر نیست.

تخصیص متغیرهای غیرمستقیم/مرجع

گاهی اوقات به مقصود نوشتن اطلاعات در یک مکان قابل پیکربندی به طور پویا، مایل هستید از یک متغیر به دیگری اشاره کنید. به طور نوعی موقعی این اتفاق می‌افتد که شما برای نوشتن تابع «قابل استفاده مجدد» تلاش می‌نمایید، و می‌خواهید خروجی‌اش را در یک متغیر انتخابی فراخوان کننده، به جای متغیر انتخابی تابع، قرار بدهد. (قابلیت استفاده مجدد توابع پوسته در بهترین حالت خود، مورد تردید است، پس این موردی است که غالباً نباید استفاده شود.)

اختصاص مقدار از طریق مرجع(یا اشاره‌گر، یا متغیر غیرمستقیم، یا هر چه شما می‌خواهید آنرا بنامید -- من از اینجا به بعد می‌خواهم از "ref" استفاده کنم) به طور گسترده‌تری ممکن است، اما توانایی انجام آن به شدت مختص پوسته است.

قبل از اینکه شروع کنیم، باید اشاره کنیم که شما باید مقدار ref را کنترل کنید. یعنی، شما باید فقط از آن ref که مقدار آن را خودتان داخل برنامه تخصیص داده‌اید، یا از ورودی تصدیق شده، استفاده کنید. اگر یک کاربرنهایی بتواند متغیر ref را با رشته‌های دلخواه اشغال کند، نتیجه می‌تواند تزریق پیش‌بینی نشده کُد باشد. در انتها مثالی از این مورد را نشان خواهیم داد.

در ksh93، فقط می‌توانیم دوباره از nameref استفاده کنیم:

  •  # ksh93
     nameref ref=realvariable
     ref="contents"
     #   می‌باشد "contents" شامل رشته  realvariable اکنون

در Bash، می‌توانیم از read و ترکیب دستوری here string ویژه Bash استفاده کنیم:

  •  # Bash
     ref=realvariable
     IFS= read -r $ref <<< "contents"
     #   می‌باشد "contents" شامل رشته  realvariable اکنون

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

ترفند مشابهی نیز برای متغیرهای آرایه‌ای Bash کار می‌کند:

  •  # Bash
     aref=realarray
     read -r -a $aref <<< "words go into array elements"
     echo "${realarray[1]}"   # را چاپ می‌کتد "go"

(یک بار دیگر، سطر جدید در ورودی این ترفند را نقض خواهد نمود. متغیر IFS برای جداکردن کلمان به کار می‌رود، بنابراین شما شاید به تنظیم آن احتیاج داشته یا نداشته باشید.)

ترفند دیگر مورد استفاده، فرمان ‎ printf -v‎ در Bash است(فقط در نگارش‌های اخیر در دسترس می‌باشد):

  •  #  یا بالاتر Bash 3.1 نگارش 
     ref=realvariable
     printf -v $ref %s "contents"

اگر محتویات شامل یک رشته ثابت نباشد، بلکه ترجیحاً تولید شده به طور پویا باشد، ترفند ‎printf -v‎ سودمند است. از تمام امکانات قالب‌بندی printf می‌توانید استفاده کنید. این ترفند هر محتوایی را نیز برای رشته اجازه می‌دهد، از جمله سطرهای‌جدید تعبیه شده(اما بایتهای NUL خیر -هیچ نیرویی در جهان نمی‌تواند بایتهای تهی را به طور قابل استفاده‌ای در رشته‌های شل قرار بدهد). اگر شما در bash نگارش 3.1 یا بالاتر هستید، این بهترین ترفند است.

بازهم یک ترفند دیگر فرمان typeset پوسته Korn یا فرمان declare پوسته Bash است. اینها تقریباً هم‌ارز یکدیگر هستند. هر دوی اینها اگر داخل یک تابع به کار بروند باعث قلمرو محلی متغیر می‌شوند، اما اگر در خارج از تابع استفاده شوند، می‌توانند متغیرهای سراسری را به کار اندازند.

  •  # Korn تمام نگارشهای پوسته
     typeset $ref="contents"
    
     # Bash:
     declare $ref="contents"

نگارش 4.2 Bash گزینه ‎ declare -g‎ را اضافه نموده، که می‌تواند حتی از داخل تابع متغیر را در زمینه سراسری قرار بدهد.

مزیت استفاده از typeset یا declare نسبت به eval آنست که سمت راست تخصیص توسط شل تفکیک نمی‌شود. اگر شما در اینجا از eval استفاده کنید، باید اول تمام سمت راست تخصیص راپاکیزه کنید(پوشش بدهید). این ترفند محتویات را نیز به طور دقیق حفظ می‌نماید،از جمله سطرهای جدید را، بنابراین، اگر در bash قبل از 3.1 (یا ksh88) می‌باشید بهترین ترفند است و در مورد تغییر تصادفی حوزه متغیرها نگران نباشید(به عنوان مثال، در حال استفاده از آن در داخل تابع نیستید).

در هرحال، با bash، هنوز هم شما باید در مورد سمت چپ تخصیص مراقب باشید. داخل کروشه‌ها، بازهم بسط ها انجام می‌شوند، بدین معنی که با ref آلوده، declare می‌تواند درست به خطرناکی eval باشد:

  •  # Bash:
     ref='x[$(touch evilfile; echo 0)]'
     ls -l evilfile   # چنین فایل یا دایرکتوری موجود نیست
     declare "$ref=value"
     ls -l evilfile   # ! حالا موجود است

این مشکل با typeset در mksh و pdksh نیز وجود دارد، اما ظاهراً در ksh93 نیست. این است چرای آنکه مقدار ref باید همیشه تحت کنترل شما باشد.

اگر شما از پوسته Bash یا Korn استفاده نمی‌کنید، می‌توانید تخصیص متغیرهای مرجع شده را با استفاده ازترکیب دستوری HereDocument انجام دهید:

  •  # Bourne
     ref=realvariable
     read $ref <<EOF
     contents
     EOF

(افسوس، با روش read ما برای گرفتن حداکثر تنها یک سطر محتوا عقب‌گرد می‌کنیم. این قابل حمل‌ترین ترفند است، اما محدود به یک سطر منفرد محتوا شده است.)

به خاطر بیاورید که، موقع استفاده از here document، اگر کلمه نگهبان(EOF در مثال ما) نقل‌قولی نشده باشد، آن وقت بسط پارامتر درون بدنه انجام خواهد شد. اگر نگهبان نقل‌قولی بشود، سپس بسط پارامتر انجام نمی‌شود. هر کدام که برای کار شما مناسبتر است به کار ببرید.

سرانجام، بعضی ها دقیقاً نمی‌توانند با پرتاب eval داخل یک عکس نیز مخالفت کنند:

  •  # Bourne
     ref=myVar
     eval "$ref=\$value"

این به جمله‌ای که اجرا می‌شود بسط می‌یابد:

  •  myVar=$value

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

خبر خوب آن است که اگر شما طرف راست را به طور صحیح سترون نمایید، این ترفند کاملاً قابل حمل است،هیچ مسئله ناشی از دامنه متغیر ندارد، و تمام محتویات از جمله سطرهای جدید را اجازه می‌دهد. خبر بد آنکه اگر شما در سترون‌سازی طرف راست به طور صحیح، موفق نباشید، حفره حجیم امنیتی دارید. از eval با ریسک خودتان استفاده کنید.

See Also


CategoryShell

پرسش و پاسخ شماره ۶ (آخرین ویرایش ‎ 2012-05-28 18:03:15 ‎ توسط ormaaj)

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



چطور می‌توانم از متغیرهای متغیر( متغیرهای غیرمستقیم، اشاره‌گرها، مرجع‌ها ) یا آرایه‌های انجمنی استفاده کنم؟

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

مندرجات

  1. چطور می‌توانم از متغیرها( متغیرهای غیر مستقیم، اشاره‌گرها، مرجع‌ها) یا آرایه‌های انجمنی استفاده کنم؟
    1. آرایه‌های انجمنی
      1. آرایه انجمنی در پوسته‌های قدیمی‌تر هک می‌شود
    2. مقدمه
      1. قبل از به کار بردن متغیر غیر مستقیم، فکر کنید
      2. ارزیابی متغیرهای غیر مستقیم/مرجع
      3. تخصیص متغیرهای غیرمستقیم و مرجع
    3. See Also

آرایه های انجمنی

ما اول آرایه‌های انجمنی را معرفی می‌کنیم، زیرا در عمده موقعیت‌هایی که افراد سعی در تخصیص و ارزیابی متغیرهای غیرمستقیم می‌نمایند، به جای آن باید از آرایه‌های انجمنی استفاده کنند. برای نمونه، ما بارها کسانی را دیده‌ایم که می‌پرسند چطور می‌توانند یک گروه متغیرهای وابسته مانند IPaddr_hostname1، IPaddr_hostname2 و به همین ترتیب، داشته باشند. روش مناسبتر برای ذخیره این داده، یک آرایه انجمنی به نام IPaddr است که با hostname(نام میزبان) شاخص‌گذاری شده باشد.

برای طرح‌ریزی از یک رشته به دیگری، به آرایه شاخص‌گذاری شده توسط رشته به جای عدد، نیاز دارید. این در AWK به عنوان «آرایه‌های انجمنی» موجود است، در پرل به عنوان«hashes»، و در Tcl به سادگی به عنوان«arrays». آنها همچنین در ksh93 وجود دارند، که در آنجا شما از آن به این شکل استفاده می‌کنید:

  •  # ksh93
     typeset -A homedir             # تعریف می‌کند ksh93 آرایه انجمنی
     homedir[jim]=/home/jim
     homedir[silvia]=/home/silvia
     homedir[alex]=/home/alex
    
     for user in "${!homedir[@]}" 
     # تمام شاخص‌ها(نامهای کاربری) را به شمار می‌آورد
     do
         echo "Home directory of user $user is ${homedir[$user]}"
     done

BASH از نگارش 4 و بالاتر، از آنها پشتیبانی می‌کند:

  •  # Bash 4 and up
     declare -A homedir
     homedir[jim]=/home/jim
     # یا
     homedir=( [jim]=/home/jim
               [silvia]=/home/silvia
               [alex]=/home/alex )
     ...

در نسخه‌های Bash قبل از نگارش 4 یا اگر نمی‌توانید از ksh93 استفاده کنید، گزینه‌های شما محدود است. یا به سوی سایر مفسرها(awk، perl، python، ruby، tcl، ...) بروید یا مشکل خود را با ساده سازی آن ارزیابی مجدد نمایید.

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

فرض کنید چند میزبان خادم با مختصر تفاوتی در پیکربندی آنها داریم، و می‌خواهیم با ssh در هر یک از آنها فرمانهایی با تفاوت جزئی را اجرا نماییم. یک روشی که می‌توانیم به کار ببریم، تنظیم یک گروه فرمانهای ssh، که به آسانی نمی‌توانند تغییر کنند(hard-code)، در توابع هر نام میزبان در یک اسکریپت منفرد و فقط اجرای آنها به طور سری یا موازی. (فوراً این را رد نکنید! ساده خوب است.) روش دیگر می‌تواند ذخیره هر گروه از دستورات به عنوان یک عضو آرایه انجمنی شاخص‌گذاری شده با نام میزبان باشد:

  •  source "$conf"
     for host in "${!commands[@]}"; do
         ssh "$host" "${commands[$host]}"
     done
    
     # :فایلی مشابه این است  "$conf" که در آن
     declare -A commands
     commands=( [host1]="mvn clean install && cd webapp && mvn jetty:run"
                [host2]="..."
     )

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

بنابراین، اغلب سزاوار است یک قدم به عقب برگردیم و به جای سایر زبانهای برنامه‌نویسی، به ضوابط پوسته ها فکر کنیم . آیا در حال اجرای اسکریپت در یک میزبان راه دور نیستیم؟ پس چرامجموعه پیکربندی‌ها را در اسکریپت‌ها ذخیره نمی‌کنیم؟ آنوقت ساده است:

  • #مشخص برای میزبانهایی که دستورات باید در آنها اجرا شوندconfیک سری فایل 
     for conf in /etc/myapp/*; do
         host=${conf##*/}
         ssh "$host" bash < "$conf"
     done
    
     # /etc/myapp/hostname is just a script:
     mvn clean install &&
     cd webapp &&
     mvn jetty:run

اکنون ما نیاز به آرایه‌های انجمنی ، و همچنین نیاز به حل گروهی از مسائل بسیار ناگوار نقل‌قولی را برطرف نموده‌ایم. موازی‌سازی با Parallel گنو نیز آسان است:

  •  parallel ssh {/} bash "<" {} ::: /etc/myapp/*

آرایه انجمنی در پوسته‌های قدیمی‌تر قابل مدیریت است

قبل از اندیشیدن به استفاده از eval برای تقلید آرایه‌های انجمنی در یک پوسته‌ قدیمی‌تر(احتمالاً با ایجاد مجموعه‌ای از نام متغیرها مشابه homedir_alex)، فکر کردن به رویکرد ساده‌تر یا کاملاً متفاوتی که می‌توانید به جای آن به کار ببرید را امتحان کنید. اگر بازهم به نطر می‌رسد که این دستیابی بهترین کار است،معایب ذیل را ملاحظه نمایید:

  1. حقیقتاً خواندن، پیگردی نمودن، و نگهداری آن دشوار است.
  2. نامهای متغیر باید با عبارت منظم ^[a-zA-Z_][a-zA-Z_0-9]* منطبق باشد-- برای مثال، یک نام متغیر نمی‌تواند شامل کاراکترهای دلخواه باشد بلکه فقط حروف، ارقام و خط‌زیر مجاز می‌باشند. نمی‌توانیم متغیری شامل نامهای کاربری یونیکس داشته باشیم، برای نمونه، -- نام کاربری hong-hu را ملاحظه کنید. خط تیره '-'نمی‌تواند بخشی از نام متغیر باشد،بنابراین تمام تلاش برای ایجاد متغیری به نام homedir_hong-hu از ابتدا محکوم به شکست است.

  3. نقل‌قول برای حصول نتیجه صحیح دشوار است. اگر محتوای رشته(نه نام متغیر) می‌تواند شامل کاراکترهای فضای سفید و نقل‌قول باشد،نقل‌قولی نمودن به طور صحیح برای حفظ آن در هر دو تجزیه پوسته دشوار است. و آن فقط برای ثابت‌ها که در زمان نوشتن برنامه معلوم هستند، شدنی است. (فرمان ‎ printf %q‎ پوسته Bash کمک می‌کند، اما مورد قابل مقایسه‌ای در پوسته‌های POSIX در دسترس نیست

  4. اگر برنامه گندزدایی، ورودی کاربر را اداره نکند، این می‌تواند خیلی خطرناک باشد!

بخش آرایه‌ها از راهنما یا پرسش و پاسخ 5 را برای توضیح عمقی و مثالهایی از چگونگی استفاده از آرایه‌ها در Bash، بخوانید.

اگر شما نیاز به یک آرایه انجمنی دارید اما پوسته شما آنها را پشتیبانی نمی کند، لطفاً به جای آن استفاده از AWK را در نظر بگیرید.


ادامه دارد ....

ادامه پرسش و پاسخ شماره ۵

ادامه پرسش و پاسخ شماره ۵

2.1.2. سایر روشها

گاهی اوقات زدودن سطرهای خالی واقعاً مطلوب است، یا شاید شما بدانید که همواره ورودی با سطرجدید جدا شده است، از قبیل ورودی تولید شده به طور درونی توسط اسکریپت شما. در برخی پوسته‌ها، امکان استفاده از ‎ -d‎ برای تنظیم جداکننده سطر read به null، و بعد سوء استفاده از ‎ -a‎ یا ‎-A‎(نسبت به پوسته) -- که به طور معمول برای خواندن فیلدهای یک سطر به داخل آرایه به کار می‌رود -- برای خواندن سطرها، وجود دارد. به طورقابل اجرا، با تمام ورودی به عنوان یک سطر رفتار می‌شود، و فیلدهای آن با سطرجدید جدا می‌شوند.

# Bash 4  در شل
    IFS=$'\n' read -rd '' -a lines <file

# mksh و zsh  در شل 
    IFS=$'\n' read -rd '' -A lines <file

متأسفانه، مورد فوق در ksh93 کار نمی‌کند، ولواینکه فرمان read در این پوسته نشانه جداکننده ‎ -d‎ را دارد. البته، مثالهای فوق سطرهای خالی را حفظ نمی‌کنند، اما آنها جایگزین سریع و آسان mapfile هستند که در چند پوسته غیر-bash نیز کار می‌کنند. از نگارش ‎ alpha 2012-10-12 ‎  شل ksh93 برطرف گردیده است

12-10-09 +read -d '' now reads up to a NUL byte.

2.1.3. سطرها را با for نخوانید!

هرگز سطرها را با استفاده از for در حلقه‌ها نخوانید! متکی بودن به تفکیک کلمه متغیر IFS، اگر فضای سفید جداکننده تکراری داشته باشید، باعث مسائلی می‌گردد، به دلیل آنکه آنها یکپارچه می‌شوند. به این طریق امکان حفظ نمودن سطرهای سفید به عنوان عناصر خالی آرایه وجود ندارد. حتی بدتر از این، کاراکترهای ویژه جانشینی، بدون غیرفعال کردن و فعال نمودن مجدد آن، بسط داده می‌شوند. هرگز از این راهکار استفاده نکنید، مسئله‌ساز است، روشهای عبور موقت همگی ناخوشایند هستند، و تمام مشکلات حل نمی‌شوند.

چون که چنین موردی به طور غیر قابل باوری، یک اشتباه رایج می‌باشد، مورد ذیل تقریباً بهترین بیان از این ترفند و اینکه چقدر سخت‌تر از انجام آن به طور صحیح است، را تشریح می‌کند-- و باز هم نمی‌تواند سطرهای جدید متوالی را حفظ نماید! ناسالمی‌اش از اینجا ناشی می‌شود. برای توضیحات تفصیلی بخش سطرهای جدید را با for نخوانید را ملاحظه کنید.

# Bash
# WARNING: Don't do this!

evilReadLines() {
    [[ -e $2 ]] || return

    # را حفظ کند trap  به سختی تلاش می‌کند جانشین قبلی و وضعیتهای ‎
    # را تنظیم کند بازهم در زحمت است ERR یا DEBUG اما اگر فراخواننده 
    if [[ $- != *f* ]]; then
        set -f
        local oReturn=$(trap -p RETURN)
        trap 'set +f; trap "${oReturn:--}" RETURN' RETURN
    fi

    local line idx IFS=$'\n'
    for line in ${1:+$(<"$2")}; do
        printf -v "${1}[idx++]" %s "$line"
    done

 #:این یک جایگزین برای حلقه فوق، همان اندازه نامساعد، گرچه اندکی سریعتر
 # IFS=$'\n' declare -a ${1:+"$1"'=( $(<"$2") )'} 2>/dev/null
}
declare -ft evilReadLines # .را از فراخوانی کننده به ارث می‌برد trapها ‎

 # نامها و نام‌فایلها را به آرایه عبور می‌دهد
evilReadLines myArray myFile

2.2. خواندن جریانهای شامل جداکننده NUL

اگر با رکوردهایی سر و کار دارید که سطرهای جدید می‌توانند در آنها تعبیه شده باشد، شما در حال استفاده از یک جداکننده جایگزین از قبیل کاراکتر NUL‎ ( \0 )‎ برای جدا کردن رکوردها خواهید بود. در آن وضعیت، شما نیاز به استفاده ازشناسه ‎ -d‎ فرمان read نیز خواهید داشت:

# Bash
unset -v arr i
while IFS= read -rd '' 'arr[i++]'; do
    :
done < <(find . -name '*.ugly' -print0)

# or
while read -rd ''; do
    arr[i++]=$REPLY
done < <(find . -name '*.ugly' -print0)

# or (bash 3.1 and up)
while read -rd ''; do
    arr+=("$REPLY")
done < <(find . -name '*.ugly' -print0)

read -d ''‎ به Bash می‌گوید، تا رسیدن به یک بایت تهی به جای رسیدن به سطرجدید، خواندن را ادامه دهد. معلوم نیست این مطلب در تمام پوسته‌ها با ‎ -d‎ کار بکند.

2.3. الحاق به یک آرایه موجود

به طوری که قبلاً اشاره شد، آرایه‌ها پراکنده هستند - یعنی، تضمین نمی‌شود که شاخصهای عددی همجوار با کمیت‌ها اشغال شده باشند. این موضوع، آنچه به معنی «الحاق به یک آرایه موجود» می‌باشد را مغشوش می‌کند. چند رویکرد (برای الحاق)وجود دارد.

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

# Bash/ksh93
arr[++i]="new item"

اگر نمی‌خواهید یک متغیر شاخص نگهداری کنید، اما می‌دانید که آرایه شما پراکنده نیست، آنوقت می‌توانید از تعداد عناصر برای محاسبه مبدأ الحاق استفاده کنید(پیشنهاد نمی‌شود):

# Bash/ksh
# .اگر آرایه دارای حفره باشد(پرکنده باشد) ناموفق خواهد شد
arr[${#arr[@]}]="new item"

اگر نمی‌دانید آیا آرایه شما پراکنده است یا خیر، اما در نظر ندارید تمام آرایه را شاخص‌گذاری مجدد نمایید(بسیار کم بازده)، پس می‌توانید از این استفاده کنید:

# Bash
arr=("${arr[@]}" "new item")

# Ksh
set -A arr -- "${arr[@]}" "new item"

اگر در bash نگارش 3.1 یا بالاتر هستید، می‌توانید عملگر ‎ +=‎ را به کار ببرید:

# Bash 3.1, ksh93, mksh, zsh
arr+=(item 'another item')

توجه: پرانتزها لازم می‌باشند، درست مانند موقع تخصیص یک آرایه. در غیر آن صورت شما با الحاق به ‎ ${arr[0]}‎ که مترادف ‎$arr‎ است مواجه می‌شوید. اگر پوسته شما این نوع الحاق را پشتیبانی می‌کند، این روش ارجح می‌باشد.

برای نمونه‌هایی ازکاربرد آرایه جهت نگهداری دستورات پیچیده پوسته، پرسش و پاسخ شماره 50 و شماره 40 را ملاحظه کنید.

3. بازیافت کمیت‌ها از یک آرایه

${#arr[@]}‎ یا ‎${#arr[*]}‎ به تعداد عناصر آرایه بسط می‌یابند:

# Bash
shopt -s nullglob
oggs=(*.ogg)
echo "There are ${#oggs[@]} Ogg files."

عناصر منفرد به وسیله شاخص بازیابی می‌شوند:

echo "${foo[0]} - ${bar[j+1]}"

کروشه‌ها زمینه محاسبات می‌باشند. درون زمینه محاسباتی، به متغیرها، از جمله آرایه‌ها، توسط نام می‌تواند رجوع بشود. برای مثال، در بسط زیر:

${arr[x[3+arr[2]]]}

شاخص arr کمیت آن عضو آرایه x که شاخص آن 3 به اضافه مقدار‎ arr[2]‎ است، خواهد بود.

استفاده دسته جمعی از عناصر آرایه، یکی از ویژگیهای کلیدی آرایه‌های پوسته است. دقیقاً همانگونه که ‎ "$@"‎ به پارامترهای مکانی بسط می‌یابد، ‎ "${arr[@]}"‎ به لیستی از کلمات، هر عضو آرایه به یک کلمه، بسط می‌یابد. برای مثال:

# Korn/Bash
for x in "${arr[@]}"; do
  echo "next element is '$x'"
done

حتی اگر عناصر آرایه محتوی فضای سفید باشند این کُد کار می‌کند. همواره به همان تعداد کلمه منجر می‌شود که آرایه‌ای با آن تعداد عضو دارید.

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

# Bash/ksh
printf "%s\n" "${arr[@]}"

برای نسخه‌برداری اندکی پیچیده‌تر از آرایه،‎ "${arr[*]}"‎ باعث می‌شود عناصر با اولین کاراکتر متغیر IFS( یا در صورت برقرار نبودن متغیر IFS با یک فاصله) در میان آنها، به یکدیگر پیوسته شوند. در نتیجه وقوع آن، ‎"$*"‎ به همان روش پارامترهای مکانی بسط داده می‌شود.

# Bash
arr=(x y z)
IFS=/; echo "${arr[*]}"; unset IFS
# prints x/y/z

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

# Bash/ksh
arr=(x y z)
tmp=$(printf "%s<=>" "${arr[@]}")
echo "${tmp%<=>}"    # .اضافی را از انتها حذف می‌کند ‎<=>‎ 
# را چاپ می‌کند ‎x<=>y<=>z‎

یا از بُرش آرایه، که در بخش بعد شرح داده شده، استفاده کنید.

# Bash/ksh
typeset -a a=([0]=x [5]=y [10]=z)
printf '%s<=>' "${a[@]::${#a[@]}-1}"
printf '%s\n' "${a[@]:(-1)}"

این نیز نشان می‌دهد که چطور عناصر چندگانه آرایه‌های پراکنده می‌تواند در یک نوبت تخصیص داده شود. توجه نمایید استفاده از نشانه ‎ arr=([key]=value ...)‎ در میان پوسته‌ها متفاوت است. در ksh93، این ترکیب دستوری به طور پیش‌فرض به شما یک آرایه انجمنی می‌دهد مگر اینکه شما طور دیگری تعیین کنید، و کاربرد آن مستلزم تعیین کمیت یک شاخص معین به طور صریح است، برخلاف bash، که جایی که شاخص‌ها از قلم افتاده باشند از شاخص قبلی شروع می‌کند . این مثال به طریقی نوشته شده است که با هر دو سازگار باشد.

BASH نگارش 3.0 قابلیت بازیابی کمیت شاخص‌های یک آرایه را اضافه نموده:

# Bash 3.0 or higher
arr=(0 1 2 3) arr[42]='what was the question?'
unset 'arr[2]'
echo "${!arr[@]}"
# چاپ می‌کند 0 1 3 42 ‎
# (مترجم: درصورتیکه کد زیر محتوای عناصر آرایه را چاپ می‌کند نه کمیت خود شاخص‌ها را)
echo "${arr[@]}"
# را چاپ می‌کند‎  0 1 2 3  what was the question?  

بازیابی شاخص‌ها برای بعضی از انواع معین وظایف بینهایت اهمیت دارد، از قبیل نگهداری آرایه‌های موازی با شاخص‌های یکسان( روشی کم بها برای تقلید یک آرایه از structها در زبان فاقد struct است):

# Bash 3.0 or higher
unset file title artist i
for f in ./*.mp3; do
  file[i]=$f
  title[i]=$(mp3info -p %t "$f")
  artist[i++]=$(mp3info -p %a "$f")
done

#   بعداً، روی هر فایل صوتی تکرار می‌کند. حتی اگر آرایه پراکنده ‎
# . باشد، این روش کار می‌کند، فقط به شرط آنکه حفره‌های آنها یکسان باشد
for i in "${!file[@]}"; do
  echo "${file[i]} is ${title[i]} by ${artist[i]}"
done

3.1. بازیابی با اصلاحات

بسط‌های پارامتر Bash می‌تواند روی عناصر آرایه به طور دسته جمعی انجام بشود:

# Bash
arr=(abc def ghi jkl)
echo "${arr[@]#?}"          # را چاپ می‌کند bc ef hi kl
echo "${arr[@]/[aeiou]/}"   # را چاپ می‌کند bc df gh jkl

بسط پارامتر نیز می‌تواند برای استخراج عضوهای یک آرایه به کار برود. بعضی ها این را بُرش می‌نامند:

# Bash
echo "${arr[@]:1:3}"        # (دومین عنصر) ‎#1‎ سه عنصر با شروع ازعضو شماره 
echo "${arr[@]:(-2)}"       # دوعضو انتها‎
echo "${@:(-1)}"            # آخرین پارامتر مکانی‎
echo "${@:(-2):1}"          # پارامتر مکانی دوم از انتها

4. استفاده از @ همچون یک شِبه-آرایه

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

# POSIX
set -- *.mp3
if [ -e "$1" ]; then
  echo "there are $# MP3 files"
else
  echo "there are 0 MP3 files"
fi

# POSIX
...
# .یک گزینه به لیست گزینه‌های ما که به طور پویا تولید شده‌اند، اضافه می‌کند
set -- "$@" -f "$somefile"
...
foocommand "$@"

(با دستورات تولید شده به طور پویا در پرسش و پاسخ 50 با استفاده از آرایه‌های نام گذاری شده، مقایسه کنید.)

See Also


CategoryShell

پرسش و پاسخهای Bash -شماره 5 (آخرین ویرایش ‎ 2012-12-01 15:17:42 ‎ توسط 148)


پرسش و پاسخ شماره ۵



چگونه می‌توانم از متغیرهای آرایه‌ای استفاده کنم؟

این پاسخ فرض بر این دارد که شما در درجه اول، دارای درک اساسی از اینکه آرایه‌ها چه می‌باشند، هستید. اگر شما در این نوع برنامه‌نویسی، تازه وارد هستید،شاید بهتر باشد با توضیح راهنما شروع نمایید. صفحه کامل و دارای جزئیات بیشتری است.

1. مقدمه

آرایه‌های یک بُعدی با شاخص عدد صحیح در پوسته Bash، Zsh، واکثر پوسته‌های متنوع Korn از جمله‎ AT&T ksh88 ‎ یا مابعد از آن، mksh، و pdksh پیاده‌سازی گردیده‌اند. آرایه‌ها توسط POSIX تعریف نشده‌اند و در میراث آن یا پوسته‌های حداقلی، مانند شل Bourne Dash در دسترس نمی‌باشند. پوسته‌های سازگار با POSIX که معمولاً در اصول اساسی آرایه‌ها توافق دارند، ولی در جزئیات تفاوتهای عمده‌ای دارند. کاربران پیشرفته پوسته‌های متعدد، باید با تحقیق نمودن ویژگی‌ها، اطمینان حاصل نمایند. Ksh93، Zsh، و Bash نگارش 4.0 علاوه براین دارای آرایه‌های انجمنی نیز می‌باشند. این مقاله بر آرایه‌های شاخص‌دار تمرکز می‌نماید، چون آنها رایج‌ترین و سودمندترین نوع هستند.

این هم کاربرد نوعیِ نمایان کننده الگو، در ‌آرایه‌ای به نام host:

# Bash  در پوسته‎

# .به آرایه‌ای با شاخص‌های ترتیبی goofy و mickey, minnie  اختصاص مقادیر
host=(mickey minnie goofy)

#  "host"تکرار روی شاخص های 
for idx in "${!host[@]}"; do
    printf 'Host number %d is %s' "$idx" "${host[idx]}"
done

"${!host[@]}"‎ به شاخص های آرایه host بسط می‌یابد، هر یک به صورت یک شناسه جداگانه. (در ادامه وارد جزئیات ترکیب دستوری--syntax-- خواهیم شد.)

آرایه‌های شاخص‌دار پراکنده هستند، و عناصر آنها می‌توانند به طور نامرتب حذف و اضافه بشوند.

# Bash و ksh   در پوسته‎

# .ترکیب دستوری ساده
arr[0]=0
arr[2]=2
arr[1]=1
arr[42]='what was the question?'

# (مترجم: با شروع از صفر، می‌شود سومین عضو ) "arr" حذف عضو شماره دو
unset -v 'arr[2]'

# الحاق عناصر به طوری که با فاصله جداشده‌اند در یک رشته منفرد و نمایش آن
echo "${arr[*]}"
# "0 1 what was the question?" :خروجی 

حتی اگر گمان می‌کنید می‌توانید تضمین کنید که هرگز حفره‌ای وجود نخواهد داشت، این یک تمرین مناسب برای نوشتن کُد به طریقی می‌باشد که بتواند آرایه پراکنده را مدیریت نماید. تنها در صورتی با آرایه‌ها به عنوان لیست رفتار کنید که مطمئن باشید، و جلوگیری از پیچیدگی تا اندازه‌ای باشد که این عمل را توجیه نماید.

2. بارگذاری کمیت‌ها در یک آرایه

اختصاص یک عضو آرایه در یک نوبت، ساده و قابل حمل است:

# Bash و ksh  در پوسته
arr[0]=0
arr[42]='the answer'

تخصیص چندگانه کمیت‌ها به آرایه، در یک نوبت نیز امکان پذیر است، اما ترکیب دستوری آن در میان پوسته‌ها متفاوت است. Bash فقط از ترکیب ‎ arrName=(args...) ‎  پشتیبانی می‌کند. ksh88 فقط از ترکیب ‎set -A arrName -- args...‎ پشتیبانی می‌نماید. ksh93، mksh، و zsh از هردو پشتیبانی می‌کنند. اگر از نزدیک به آن نگاه کنید، در هر دو روش تفاوتهای ظریفی بین تمام این پوسته‌ها وجود دارد.

# Bash, ksh93, mksh, zsh  در پوسته‌های
array=(zero one two three four)

# ksh88/93, mksh, zsh  در پوسته‌های 
set -A array -- zero one two three four

موقع مقدار دهی اولیه به این طریق، اولین شاخص صفر است مگر اینکه شاخص دیگری تعیین شده باشد.

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

# Bash و ksh93  در پوسته
oggs=(*.ogg)

در روش تخصیص ksh88 مانند، با استفاده از set، شناسه‌ها دقیقاً مانند شناسه‌های معمولی فرمان هستند.

# Korn  در پوسته
set -A oggs -- *.ogg

#                     (بسط ابرو نیازمند نگارش 3.0 یا بالاتر است) Bash در ‎
homeDirs=(~{,root})
#      است Bash به ترتیب دیگری رخ می‌دهد و این روش محصوص ksh بسط ابرو در‎
letters=({a..z})   # در همه شل‌ها نمی‌توان با بسط-رشته حروف را به کار برد

# Korn  در پوسته
set -A args -- "$@"

2.1. بارگیری سطرها از فایل یا جریان داده

در bash نگارش 4، فرمان mapfile(که readarray نیز نامیده می‌شود) این موارد را انجام می‌دهد:

# Bash 4 در 
mapfile -t lines <myfile

# یا
mapfile -t lines < <(some command)

بخش جایگزینی پردازش و پرسش و پاسخ شماره ۲۴ را برای جزئیات بیشتر در باره ترکیت دستوری ‎ <(...)‎ ملاحظه کنید.

فرمان mapfile سطرهای خالی و همچنین فقدان سطر جدید انتهای یک جریان ورودی را با درج آنها به عنوان یک عضو تهی آرایه اداره می‌کند. این مورد، موقعی که داده‌ها به روش دیگری( بخش بعدی را ببینید) خوانده می‌شوند، می‌تواند مشکل ساز بشود. mapfile یک سری اشکال دارد: این فرمان فقط سطر جدید را می‌تواند به عنوان خاتمه دهنده سطرها مدیریت کند. تمام گزینه‌های مورد پشتیبانی فرمان read توسط mapfile، و به طور معکوس، مدیریت نمی‌شوند. به عنوان مثال mapfile نمی‌تواند فایلهای جداشده با کاراکتر NUL بواسطه فرمان find -print0`‎ را مدیریت کند. موقعی که mapfile در دسترس نباشد، برای تهیه المثنی آن باید خیلی سخت کار کنیم. روشهای بسیاری برای تهیه تقریباً موفق آن وجود دارد، اما به طرُق ظریفی شکست می‌خورند.

این مثالها رونوشتی از اکثر توانایی‌های اساسی mapfile ایجاد می‌کنند:

# Bash, Ksh93, mksh  در پوسته‌های
while IFS= read -r; do
    lines+=("$REPLY")
done <file
[[ $REPLY ]] && lines+=("$REPLY")

عملگر ‎+=‎ وقتی با پرانتزها به کار می‌رود، عنصری به آرایه اضافه می‌کند، که شماره شاخص آن برابر بالاترین شاخص موجود آرایه به اضافه یک خواهد بود.

# Korn  در پوسته‎
#  .کاهش و افزایش قبل و بعد را پشتیبانی نمی‌کند  Ksh88  
i=0
while IFS= read -r; do
    lines[i+=1,$i]=$REPLY
done <file
[[ $REPLY ]] && lines[i]=$REPLY

کروشه‌ها زمینه محاسباتی ایجاد می‌کنند. نتیجه عبارت، شاخص مورد استفاده برای تخصیص است.

2.1.1. مدیریت سطرهای جدید(یا فقدان آنها) در انتهای فایل

قرمان read موقعی که آخرین سطر فایل را می‌خواند false را باز می‌گرداند. این مشکلی را بیان می‌کند: اگر فایل شامل سطر جدید در انتها باشد، آنوقت read موقع خواندن و تخصیص سطر انتهایی غلط خواهد بود، در غیر آنصورت موقع خواندن و تخصیص آخرین سطر داده، نادرست خواهد بود. بدون یک کنترل خاص برای این حالتها، صرف نظر از منطق به کار رفته، همیشه یا آرایه حاصل با یک عضو خالی اضافی، یا با فقدان عضو پایانی خاتمه خواهد یافت.

برای واضح شدن مطلب- اکثر فایلهای متن شامل یک سطر جدید به عنوان آخرین کاراکتر فایل می‌باشند. سطرجدید توسط اکثر ویرایشگرهای متن به انتهای فایلها اضافه می‌شود، و همچنین توسطHere documentها و Here stringها نیز اضافه می‌شوند. اکثر اوقات، فقط موقع خواندن خروجی از لوله‌ها و جایگزینی پردازش، یا از فایلهای متن معیوب تولید شده توسط ابزارهای معیوب یا فاقد پیکربندی مناسب، این مورد یک مسئله است. اجازه دهید به چند مثال نگاه کنیم.

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

# !به طور صحیح عمل نمی‌کند
unset -v arr i
while IFS= read -r 'arr[i++]'; do
    :
done < <(printf '%s\n' {a..d})

متأسفانه، اگر فایل یا جریان داده شامل یک سطر جدید انتهایی باشد، یک عضو خالی به انتهای آرایه اضافه می‌شود، زیرا دستور‎ read -r arr[i++]‎ بعد از آخرین سطر شامل متن و قبل از باز گرداندن غلط، یک مرتبه اضافی اجرا می‌شود.

# !باز هم به طور صحیح عمل نمی‌کند
unset -v arr i
while read -r; do
    arr[i++]=$REPLY
done < <(printf %s {a..c}$'\n' d)

کروشه‌ها زمینه محاسباتی ایجاد می‌کنند. داخل آنها، ‎i++‎ همانطور عمل می‌کند که برنامه‌نویسان C انتظار دارند(همه غیر از ksh88).

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

# Bash, ksh93, mksh  در پوسته های
unset -v arr i
while IFS= read -r; do
    arr[i++]=$REPLY
done <file
# .الحاق سطر داده خاتمه نیافته، در صورت موجود بودن
[[ $REPLY ]] && arr[i++]=$REPLY 

این مورد به «آخرین راه حل» که ما قبلاً ارائه نمودیم، بسیار نزدیک است -- هم مدیریت سطرهای خالی داخل فایل، و هم سطر انتهایی خاتمه نیافته. IFS تهی برای ممانعت فرمان read از زدودن فضای سفید احتمالی ابتدا و انتهای سطرها در صورتی که مایل به حفظ آنها باشید، به کار می‌رود .

یک طریق دیگر عبور از این مشکل، حذف عضو خالی پس از پایان حلقه است:

# Bash
unset -v arr i
while IFS= read -r 'arr[i++]'; do
    :
done <file

# .عضو انتهایی را اگر باشد حذف می‌کند
[[ ${arr[i-1]} ]] || unset -v 'arr[--i]'

خواه شما ترجیح بدهید مقدار اضافه بخوانید و سپس یکی را حذف کنید، یا کمتر بخوانید و بعد یکی را اضافه کنید، یک انتخاب شخصی است.

توجه: برای آنکه کروشه‌ها به عنوان globها تفسیر نشوند، نقل‌قولی نمودن ‎ 'arr[i++]'‎ تحویل شده به فرمان read ضروری است. این مظلب برای سایر دستورات داخلی غیر کلیدواژه‌ای که یک نام متغیر زیرنویس‌دار می‌گیرند مانند let و unset نیز صدق می‌کند.


ادامه دارد ....

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



4. چگونه بررسی نمایم که آیا یک شاخه، خالی است یا خیر؟ چطور هر فایل ‎*.mpg‎ را بررسی کنم، یا تعداد آنها را شمارش کنم؟

در Bash، می‌توانید این کار را به طور مطمئن و به آسانی با گزینه‌های nullglob و dotglob (که رفتار globbing را تغییر می‌دهند) و یک آرایه انجام دهید:

پاسخ مشروح را در اینجا ‎[BashFAQ/004]‎ ملاحظه نمایید: