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

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

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

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

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

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

چطور می‌توانم در یک حالت ایمِن، یک فایل موقتی ایجاد کنم؟

به نظر نمی‌رسد هیچ فرمان منفردی وجود داشته باشد که واقعاً در همه جا کار بکند. tempfile قابل حمل نیست. mktemp به طور وسیع‌تری موجود است( اما باز هم به طور در همه جا حاضر نیست)، فقط ممکن است برای ایجاد فایل از قبل، مستلزم گزینه ‎ -c‎ باشد، یا ممکن است به طور پیش‌فرض فایل را ایجاد کند و اگر ‎ -c‎ فراهم شده باشد، ناموفق گردد. برخی سیستم‌ها هیچ یک از دو فرمان را ندارند(سولاریس و POSIX).

پاسخ سنتی به طور معمول چیزی مانند این بوده:

  #   استفاده نکنید! وضعیت مسابقه!
   tempfile=/tmp/myname.$$
   trap 'rm -f "$tempfile"; exit 1' 1 2 3 15
   rm -f "$tempfile"
   touch "$tempfile"

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

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

متأسفانه، به نظر نمی‌رسد افراد این پاسخ را دوست داشته باشند. آنها خواستار آن هستند که فایلهای موقتی‌شان در ‎/tmp‎ یا ‎/var/tmp‎ باشند. برای آن افراد، روش پاکیزه‌ای وجود ندارد، بنابراین آنها باید روش هوشمندانه(hack)ای که می‌توانند با آن ادامه بدهند را انتخاب کنند.

ساختن یک دایرکتوری موقتی

در برخی سیستم‌ها(مانند لینوکس):

  • شما دارای فرمان معتبر mktemp می‌باشید و می‌توانید از گزینه ‎-d‎ آن استفاده نمایید، برای اینکه دایرکتوری‌های موقتی ایجاد می‌کند که تنها برای شما قابل دستیابی است، همراه با کاراکترهای تصادفی در نام آنها جهت تقریباً غیر ممکن نمودن حدس زدن پیشاپیشن نام دایرکتوری برای یک مهاجم.

  • می‌توانید فایلهایی با نام طولانی‌تر از ۱۴ کاراکتر در ‎/tmp‎ ایجاد کنید. ‎

  • شما Bash را در دسترس دارید بنابراین از ویژگی‌های مخصوص آن برای این کار استفاده کنید.

   # POSIX در‎

   # تنظیم می‌کند ‎"/tmp"‎  از قبل دارای مقدار نباشد آن را به ‎$TMPDIR‎ فقط در صورتیکه‎
   : ${TMPDIR:=/tmp}

   # ببینیم ‎http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html‎ این صفحه را ‎$TMPDIR‎ می‌توانیم در باره‎

   # ایجاد کنید ‎$TMPDIR‎ یک دایرکتوری موقتی خصوصی در‎
   # وقتی اسکریپت خاتمه می‌یابد دایرکتوری موقتی را حذف کنید‎
   unset temporary_dir
   trap '[ "$temporary_dir" ] && rm -rf "$temporary_dir"' EXIT

   save_mask=$(umask)
   umask 077
   temporary_dir=$(mktemp -d "$TMPDIR/XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") || { echo "ERROR creating a temporary file" >&2; exit 1; }
   umask "$save_mask"

و آنوقت می‌توانید فایلهای ویژه خود را در دایرکتوری موقتی ایجاد کنید.


یک پیشنهاد متفاوت که به فرمان mktemp نیاز ندارد: با استفاده از متغیر RANDOM به صورت زیر، یک دایرکتوری موقتی که بعید است با نام یک دایرکتوری موجود مطابقت بنماید، می‌تواند ایجاد گردد:

   temp_dir=/tmp/$RANDOM
   mkdir "$temp_dir"

این کد یک دایرکتوری به شکل ‎/tmp/20445/‎ ایجاد خواهد نمود‎. برای کاهش احتمال برخورد با نام یک دایرکتوری موجود، متغیر RANDOM می‌تواند چندبار به کار برود، یا با ID پردازش ترکیب بشود:

   temp_dir=/tmp/$RANDOM-$$
   mkdir -m 700 "$temp_dir" || { echo "failed to create temp_dir" >&2; exit 1; }

این کد یک دایرکتوری به صورت ‎/tmp/24953-2875/‎ ایجاد خواهد نمود. این مورد از یک موقعیت مسابقه اجتناب می‌کند، به علت اینکه mkdir همانطور که در پرسش و پاسخ ۴۵ می‌بینیم، تجزیه ناپذیر(اتمی) است. لازم‌الاجراست که شما نتیجهmkdir را کنترل نمایید، در صورتی که در ایجاد دایرکتوری ناموفق باشد ، واضح است که نمی‌توانید پیش بروید.

(برخی سیستم‌ها دارای محدودیت ۱۴ کاراکتر در نام فایل هستند، بنابراین از آزمایش ترکیب بیش از دوبارِ رشته ‎$RANDOM‎ همراه یکدیگر اجتناب کنید. شما برای امنیت خود به تجزیه ‌ناپذیری mkdir استناد می‌کنید، نه برای گمنامی نام تصادفی انتخابی. اگر شخصی دایرکتوری ‎/tmp‎ را با صدهاهزار فایلهای دارای نام عدد تصادفی به منظور انسداد راه شما، پُر کند، با مسایل بزرگتری مواجه هستید.)


به جای RANDOM، می‌توان از awk برای تولید عدد تصادفی به یک روش سازگار با POSIX استفاده نمود:

   temp_dir=/tmp/$(awk 'BEGIN { srand (); print rand() }')
   mkdir -m 700 "$temp_dir"

توجه کنید، به هر حال، آن srand()‎ سرچشمهِ تولید عدد تصادفی، از ثانیه‌ها نسبت به epoch زیرنویس 1 استفاده می‌کند که پیش بینی کردن آن برای یک متخاصم و انجام حمله پرده‌پوشی سرویس(DoS)، به طور مساعدی آسان است.

با ترکیب فوق می‌توانیم به کار ببریم:

   temp_dir=/tmp/"$(awk -v p=$$ 'BEGIN { srand(); s = rand(); sub(/^0./, "", s); printf("%X_%X", p, s) }')"
   mkdir -m 700 "$temp_dir" || { echo '!! unable to create a tempdir' >&2; exit 1; }

چون در awk قالب عددی پیش‌فرض ‎%g‎ می‌باشد که معادل شش رقم بعد از نقطه اعشار است، ما یک دنباله شش رقمی داریم. 999,999 به صورت هگز می‌شود F423F که یک کاراکتر به ما پس می‌دهد، یکی دیگر برای _ و 8 کاراکتر برای پیشوند نمودن pid در سیستم فایل ۱۴ کاراکتری در دسترس است. یک شماره شناسایی پردازش(pid) سی و دو بیتی بدون علامت، حداکثر ۸ رقم هگز خواهد شد، بنابراین مورد فوق در اکثر مکان‌هایی که ممکن است شخصی با چنین سیستم فایلی روبرو گردد، کار می‌کند. و البته چنانکه greycat اشاره نمود، موقع وجود کمبودها، از تنگنا رها می‌شویم.

  • آیا این یک مقداری غیر منطقی نمی‌باشد؟ -- GreyCat

  • نه چندان بیش از رویکرد غیرقابل حمل ارائه شده فوق. نقطه نظر مکرر شما در باره محدودیت ۱۴ کاراکتری در سیستم‌فایلها را جواب می‌دهد، و استفاده از ‎ /tmp‎ که برخی مدیران سیستم ترجیح می‌دهند را میسر می‌سازد، و در صورتی که اسکریپت یک برنامه تعریف شده با یک نام خاص نباشد، می‌تواند سودمند واقع گردد، در آن حالت من موافق هستم که داشتن یک دایرکتوری معین تحت ‎$HOME‎ یا جای دیگر، رویکرد بهتری می‌باشد. به عنوان یک موضوع فرعی، پیشوند pid درجه‌ای از کنترل یا آگاهی بر هر دایرکتوری موقتی ایجاد شده را فعال می‌کند. شخصاً ترجیح می‌دهم این مورد را در ترکیب با فراخوان‌های trap و استفاده از ‎$TMPDIR‎ ، که برای ‎POSIX sh‎ قابل حمل است را داشته باشم تا فراخوانی mktemp که من نمی‌توانم به آن تکیه کنم. -- igli

سایر راهکارها

من تصریح می‌کنم که بهترین راه‌حل برای نگهداری فایلهای اختصاصی شما استفاده از دایرکتوری ‎$HOME‎ شما می‌باشد. اگر شما در حال پیاده‌سازی یک سرویس کمکی یا موردی هستید که تحت یک حساب کاربری اجرا می‌شود، چرا به سادگی یک دایرکتوری اختصاصی برای آن سرویس در همان زمانی که آن کد را نصب می‌کنید، نسازید؟


یک پیشنهاد نه کاملاً جدی دیگر پیوست کردن کُد C در اسکریپتی است که فرمان ‎mktemp(1)‎ را برمبنای تابع کتابخانه‌ای‎ mktemp(3)‎ پیاده‌سازی میکند. اما این کار دو مشکل دارد:

  • بلااستفاده در سیستم‌های سولاریس، جایی که به این نیاز خواهیم داشت احتمالاً کامپایلر C را نداریم.
  • مسئله مرغ و تخم مرغ: ما برای نگهداری خروجی کامپایلر به نام فایل موقتی نیاز داریم.

پرسش و پاسخ 62 (آخرین ویرایش ‎2013-01-11 04:00:34 توسط 82)


  1. مترجم: عبارتی که در ابتدا درمستندات یونیکس برای ساعت وتاریخ صفر سیستم عامل به کار رفته است، و در اکثر نگارشهای یونیکس برابر نیمه شب اول ژانویه 1970 به وقت ‎UTC (برگرفته از Universal Time Coordinated‎ روش مدرن و دقیقتر از وقت گرینویچ برای محاسبه یکنواخت زمان در تمام نقاط جهان است و ساعت اتمی بین‌المللی نیز گفته می‌شود)‎ می‌باشد. ساعت سیستم که در نشانه‌های زمان فایلها ثبت می‌شود(تاریخ دستیابی، ویرایش یا ایجاد) بر اساس ثانیه‌های سپری شده از epoch است. در لغت، مبداء زمان نیز گفته می‌شود. (1)