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

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

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

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

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

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

غیر مستقیم

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

قرار دادن نام متغیرها یا هر ترکیب دستوری 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)

نظرات 0 + ارسال نظر
ایمیل شما بعد از ثبت نمایش داده نخواهد شد