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

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

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

#!/bin/bash

پرسش و پاسخ شماره ۲۸

پرسش و پاسخ شماره ۲۸

چگونه می‌توانم محل اسکریپت خود را تعیین کنم؟ می‌خواهم فایلهای پیکربندی را از همان محل بخوانم.

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

این پرسش پیچیده‌ایست، زیرا برای آن یک پاسخ صحیح منفرد وجود ندارد. حتی وخیم‌تر: پیدا کردن محل اسکریپت به طور قابل اعتماد برای‎ 100%‎ تمام حالت‌ها، امکان‌پذیر نیست. تمام روشهای یافتن محل اسکریپت وابسته به نام اسکریپت است، به طوری که در متغیر پیش‌تعریف شده ‎ $0‎ دیده شده است. اما فراهم نمودن نام اسکریپت در‎ $0 ‎ فقط یک قرارداد(بسیار رایج) است، نه یک التزام.

پاسخ مورد تردید این است: «در برخی شل‌ها ‎ $0‎ همیشه، حتی اگر شما اسکریپت را با نام مسیر نسبی، یا بدون هرگونه نام مسیر فراخوانی نمایید»، محتوی نام مسیر مطلق است. اما این در تمام پوسته‌ها قابل اتکا نیست، بعضی‌ از آنها(از جمله BASH) به جای نام مسیر کاملاً واجد شرایط، فرمان واقعی تایپ شده توسط کاربر را باز می‌گردانند. و این فقط نوک کوه یخ شناور است!

اسکریپت شما ممکن است در حقیقت به هیچ وجه در دیسک قابل دسترسی در محل قرار نداشته باشد. این مورد را ملاحظه کنید:

  ssh remotehost bash < ./myscript

پوستهِ در حال اجرا در میزبان راه دور، فرمان خود را از یک لوله دریافت می‌کند. اسکریپتی در جایی روی دیسک، که bash بتواند ببیند وجود ندارد.

علاوه براین، حتی اگر اسکریپت شما در دیسک محلی ذخیره و اجرا شده باشد، می‌تواند انتقال یابد. شخصی می‌تواند در فاصله بین زمانی که شما فرمان را تایپ می‌کنید و زمان بررسی ‎ $0‎ اسکریپت را به محل دیگری ‎mv‎ نماید. یا کسی می‌تواند در مدت همان روزنهِ زمانی، پیوند اسکریپت را قطع کند، به طوری که دیگر به طور واقعی ارتباطی با سیستم فایل نداشته باشد.

حتی در حالتهایی که اسکریپت در یک مکان ثابت روی دیسک محلی قرار گرفته است، رویکرد ‎ $0‎ باز هم اشکالات عمده‌ای دارد. مهمتر از همه آنست که نام اسکریپ(به طوری که در‎ $0‎ دیده شده) شاید نسبت به دایرکتوری کاری نباشد، بلکه وابسته به شاخه‌ای از برنامه جستجوی مسیر ‎ $PATH‎ باشد(این مورد اغلب در KornShell دیده شده است). یا(و این مشکل به مراتب محتمل‌تر است) شاید به اسکریپت پیوندهای چندگانه از مکانهای مختلف وجود داشته باشد، که یکی از آنها پیوند نمادین ساده‌ای به یک شاخه معمول در PATH مانند ‎ /usr/local/bin‎ باشد، که اسکریپت به واسطه آن فراخوانی می‌شود. اسکریپت شما شاید در ‎ /opt/foobar/bin/script‎ باشد، اما رویکرد ساده خواندن ‎ $0‎ آنرا به شما نخواهد گفت -- ممکن است به جای آن این آدرس را ‎ /usr/local/bin/script‎ بگوید.

(برای بحث جامع‌تر در خصوص فایل سیستم یونیکس و چگونگی تأثیر پیوندهای نمادین بر توانایی شما در مورد دانستن جایی که در یک لحظه معین قرار دارید، این طرح ۹ صفحه‌ای را ببینید.)

با تمام آنچه گفته شد، اگر بازهم می‌خواهید به طرف مفروضات ساده چرخش کنید، و تمام آنچه می‌خواهید، نگارش کاملاً واجد شرایط ‎ $0‎ است، می‌توانید از موردی مشابه این ترکیب دستوری استفاده کنید (BASH):

  [[ $0 == /* ]] && echo "$0" || echo "${PWD}/${0#./}"

یا نگارش پوسته Bourne:

  case $0 in /*) echo "$0";; *) echo "`pwd`/$0";; esac

یا نوع مستقل از پوسته(به ‎ readlink(1)‎ با پشتیبانی از ‎ -f‎ نیاز دارد،به هرحال وابسته به سیستم عامل است):

  readlink -f "$0"

در Bash، نگارش‎ 4.1.7(1)-release‎، در لینوکس، به نظر می‌رسد همیشه اسکریپت با توصیف‌گر‌فایل 255 باز می‌شود، بنابراین می‌توانید دقیقاً اینطور انجام بدهید:

  HOME="$(dirname "$(readlink /proc/$$/fd/255)")"

اگر بخواهیم تمام وضعیت‌هایی را بشماریم که در آنها ممکن است نام مسیر نسبی اسکریپت(در ‎ $0‎) به جای دایرکتوری کاری جاری(به طوری که در فوق اشاره گردید)،نسبت به اجزاء متشکله ‎ $PATH‎ باشد، می‌توانیم جستجو برای اسکریپت در تمام شاخه‌های ‎ $PATH‎ را آزمایش کنیم.

اسکریپت پایین چگونگی انجام آن را نشان می‌دهد:

   1 #!/bin/bash
   2 
   3 myname=$0
   4 if [[ -s "$myname" ]] && [[ -x "$myname" ]]; then
   5     # از قبل نام فایل معتبری است $myname
   6 
   7     mypath=$myname
   8 else
   9     case "$myname" in
  10     /*) exit 1;;             # جستجو نمی‌شود PATH نام مسیر مطلق،‎
  11     *)
  12         #  مراقب تفسیر ،PATH جستجوی تمام شاخه‌های متغیر ‎
  13         #  کاراکتر ":" ابتدا و انتهای مسیر به معنی دایرکتوری   ‎
  14         #  نیز همین PATH جاری باشید در مورد "::" در میان  ‎
  15         # .مطلب صادق است‎
  16 
  17         # p تعویض : ابتدای مسیر با . و ذخیره در ‎
  18         p=${PATH/#:/.:}
  19         # تعویض : انتها با .‎
  20         p=${p//%:/:.}
  21         # تعویض :: با :.:‎
  22         p=${p//::/:.:}
  23         # جداکننده فیلد موقتی، پرسش و پاسخ شماره ۱ را ببینید‎
  24         OFS=$IFS IFS=:
  25         # تفکیک مسیر نسبت به کولن و تکرار حلقه روی آنها‎
  26         for dir in $p; do
  27                 [[ -f "$dir/$myname" ]] || continue # فایل نیست‎
  28                 [[ -x "$dir/$myname" ]] || continue # قابل اجرا نیست‎
  29                 mypath=$dir/$myname
  30                 break           # فقط اولین انطباق فایل را برمی‌گرداند‎
  31         done
  32         # بازیابی جداکننده فیلد اولیه‎
  33         IFS=$OFS
  34         ;;
  35     esac
  36 fi
  37 
  38 if [[ ! -f "$mypath" ]]; then
  39     echo >&2 "cannot find full path name: $myname"
  40     exit 1
  41 fi
  42 
  43 echo >&2 "path of this script: $mypath"

توجه کنید که ‎ $mypath‎ ضرورتاً یک نام مسیر مطلق نیست. باز هم می‌تواند شامل نام مسیرهای نسبی مانند ‎ ../bin/myscript‎ باشد،زیرا ‎ $PATH‎ می‌توانست شامل آنها باشد. اگر شما می‌خواهید فقط دایرکتوری را از آن رشته به دست آورید، پرسش و پاسخ شماره 73 را بررسی کنید.

آیا متوجه می‌شوید که چطور این مشکل به طور مسخره‌ای پیچیده می‌شود؟ و این هنوز درست ساده‌ترین حالتی است که در آن، مفروضات زیادی در مورد انتقال نیافتن اسکریپت، و در لوله نبودن آن ایجاد نموده‌ایم!

به طور کلی، ذخیره فایلها در همان شاخه برنامه‌هایشان عادت نامناسبی است. طرح‌بندی فایل سیستم یونیکس فرض می‌کند که فایلهای موجود در یک محل(به عنوان مثال ‎/bin‎) برنامه‌های اجرایی هستند، در حالیکه فایلها در محل دیگر(به عنوان مثال‎ /etc‎) فایلهای داده هستند. (تصور کنید برای یک لحظه از این ارثیه سیستمهای یونیکس، با قرار دادن برنامه ها در ‎/etc‎ صرفنظر کنیم، می‌توانیم....)

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

  • واقعاً نگهداری پیکربندی اسکریپت خودتان در محل منفرد و ثابت از قبیل ‎ /etc/foobar.conf بیش از همه مفهوم واقع می‌شود.

  • اگر لازم است که چند فایل پیکربندی تعیین کنید، آنوقت می‌توانید یک دایرکتوری (مثلاً ‎ /var/lib/foobar/‎ یا ‎/usr/local/lib/foobar/‎) داشته باشید، و مکان آن دایرکتوری را از یک محل ثابت مانند‎ /etc/foobar.conf‎ بخوانید.

  • حتی اگر نمی‌خواهید که زیاد hard-coded(مترجم: نوشتن داده‌ها یا رفتار به طور مستقم در برنامه، احتمالاً در چند محل به طوری که به آسانی نمی‌تواند اصلاح شود) بشود، می‌توانید محل ‎ foobar.conf‎(یاخود دایرکتوری پیکربندی) را به عنوان پارامتر به اسکریپت بدهید.

  • اگر نیاز دارید اسکریپت در صورت فقدان ‎/etc/foobar.conf‎، پیش‌فرض معینی در نظر بگیرد، می‌توانید پیش فرض‌ها را در خود اسکریپت قرار بدهید، یا اگر ‎ /etc/foobar.conf ‎ غایب است، به موردی مانند‎ $HOME/.foobar.conf‎ بازگشت بدهید.

  • موقعی که شما اسکریپتی را در سیستم مقصد نصب می‌کنید، می‌توانید محل اسکریپت را در متغیری در خود اسکریپت قرار بدهید. مادامیکه اسکریپت تغییر مکان نیافته اطلاعات در آن محل در دسترس می‌باشد، این همیشه برای سیستم نصب شده صحیح باقی می‌ماند.
  • در اکثر وضعیت‌ها، اگر داده‌های پیکربندی شما با وسایل آشکار، نمی‌تواند یافت شود، بهتر است به طور برازنده‌ای لغو گردد تا آنکه به میان پردازشهای محرمانه برود و احتمالاً در جهت پاسخ‌های اشتباه پیش برود.
    • BASH_SOURCE احتمالاً ایده بسیار بهتری از‎ $0 ‎ است، نتایج بهتری ارائه می‌کند و بهتر تعریف می‌شود. شاید این مقاله باید با در نظر داشتن BASH_SOURCE نوشته می‌شد. --Lhunath


CategoryShell

پرسش و پاسخ 28 (آخرین ویرایش ‎ 2011-08-22 18:38:21 ‎ توسط GreyCat)