یک race condition وضعیتی است که در آن دو یا چند مورد به طور همزمان در حال وقوع میباشند، و نتیجه نهایی به زمانبندی دقیق رویدادها بستگی دارد.
برای مثال، دو برنامه را در نظر بگیرید که در یک زمان اجرا میشوند:
#!/bin/sh # برنامه اول read number < file number=$(($number + 1)) echo $number > file
#!/bin/sh # برنامه دوم read number < file number=$(($number - 1)) echo $number > file
برنامه اول، یک عدد را از فایل میخواند، در حافظه یکی به آن اضافه میکند، و آنوقت جواب را در فایل مینویسد. برنامه دوم عدد را میخواند، یک را از آن کم میکند، و سپس پاسخ را در فایل مینویسد. فرض کنید ما عدد 42 را در فایل بگذاریم و هر دو برنامه را در یک زمان اجرا کنیم. چه اتفاقی رخ میدهد؟
به طور سادهانگارانه، انتظار خواهیم داشت مقدار نهایی در فایل 42 بشود. اگر سیستم عامل ابتدا برنامه اول را زمانبندی کند، آنوقت آن برنامه 43 را در فایل مینویسد، سپس برنامه دوم 43 را میخواند، یک را از آن کم میکند و 42 را در فایل مینویسد. همچنین، اگر سیستمعامل ابتدا برنامه دوم را در زمانبندی قرار دهد، انتظار خواهیم داشت فایل محتوی 41 و سپس موقعی که برنامه اول تمام میشود، 42 بشود. درست است؟
خوب، به طور حتم این یک پیامد محتمل میباشد. اما تنها نتیجه ممکن نیست، به علت اینکه برنامهها atomic نیستند -- یعنی، آنها تمام مراحل خود را بدون انقطاع انجام نمیدهند.
یک توالی احتمالی دیگر وقوع رویدادها چنین است:
برنامه دوم 41 را در فایل مینویسد. نتیجه نهایی: 41
اگر دو سطر انتهایی به طور برعکس رخ بدهند، آنوقت جواب نهایی 43 خواهد شد. بنابراین، به نسبت تمایل زمان بندی سیستمعامل، میتوانیم نتایج انتهایی 41, 42 یا 43 را داشته باشیم.
وضعیت مسابقه در اَشکال بسیاری ظاهر میشود. در چندین نوع برنامهنویسی آنها موضوع در خورِ رسیدگی هستند. هر وقت باید کنترل کنندههای ناهماهنگ نوشته شوند، یا در یک سیستمعامل چند کاربره، با سیستمعامل فعل و انفعال صورت بگیرد، برای اجتناب از وضعیت مسابقه، باید مراقبت فوقالعاده انجام بشود.
این به طور بالقوه یک فرمان خطرناک است. آن را اجرا نکنید! راهاندازی از پرسش فوق حذف میشود، تنها آن قسمتی که تابع را تنظیم میکند باقی میماند.
بمب خوشهای یک شکل ساده تکذیب سرویس(یا حمله DoS) است که بر مبنای نام فراخوان سیستم یونیکسیِ fork(2) نام گذاری گردیده است. برنامهای است که توسط انشعاب کپیهای خودش به طور مکرر، که فرزندان نیز به طور بازگشتی همان کار را میکنند، به سرعت منابع سیستم را تحلیل میبرد. در بسیاری از سیستمهایِ بدون محدودیت صحیحِ منابع، این مورد ممکن است شما را در یک وضعیت اصلاح ناپذیر غیرپاسخگو رها کند.
این تعریف ویژه از بمب fork در Bash بنا به دلایلی چنان مشهور است که گاهی اوقات بمب خوشهای نامیده میشود.
در اینجا رایجترین شکل مورد پسند عامه کد آن آمده است:
:(){ :|:& };:
و از طرف دیگر، با قواعد مناسب برای خوانایی به این صورت:
#!/usr/bin/env bash
:() {
: | : &
}
:
این کد تابعی به نام : تعریف میکند. بدنه تابع یک لوله را تنظیم میکند، که در Bash متشکل از دو پوسته فرعی میباشد، خروجی استاندارد اولی توسط یک لوله به ورودی استاندارد دومی متصل گردیده است. تابع (پوسته والد لوله) لوله را پسزمینهای میسازد، تابع باز میگردد و پوسته با رها نمودن job در پس زمینه، خاتمه مییابد. نتیجه نهایی دو پردازش جدید است که هر کدام پردازش : را برای تکرار فرآیند، فراخوانی میکنند.
: در واقع در اکثر شرایط ( پایین تشریح شده) یک نام غیر مجاز تابع میباشد. اینجا، bomb به جای : استفاده میشود، که هم قابل حمل است و هم خوانایی بهتری دارد.
bomb() {
bomb | bomb &
}
bomb
به طور نظری، هر شخصی که در کامپیوتر شما به پوسته دسترسی دارد، میتواند از چنین تکنیکی برای نابود کردن منابعی که به آن دسترسی دارد استفاده کند. در اینجا یک chroot(2) کمک نخواهد بود. اگر منابع کاربر نامحدود باشد، آنوقت در چند ثانیهای تمام منابع سیستم شما(پردازشها، حافظه مجازی، فایلهای باز، وغیره.) استفاده خواهد شد و احتمالاً خودش دچار وقفه میشود. هر کوشش به عمل آمده توسط کرنل برای آزادسازی منابع بیشتر فقط اجازه میدهد نمونههای بیشتری از تابع ایجاد بشود.
در نتیجه، تنها راه محافظت از خودتان در برابر چنین سوءاستفادهای، محدودیتِ حداکثرِ مجازِ استفاده از منابع برای کاربرانتان میباشد. چنین منابعی به وسیله فراخوان سیستمی setrlimit(2) مقرر میشود. واسط این قابلیت در Bash و پوسته Korn فرمان ulimit است. همچنین ممکن است سیستم عامل شما فایلهای پیکربندی ویژهای برای کمک به مدیریت این منابع داشته باشد( برای مثال، فایل /etc/security/limits.conf در دبیان، یا /etc/login.conf در OpenBSD). برای جزئیات، مستندات سیستم خود را کنکاش نمایید.
این تعریف عامه پسند به علت یک ترکیب غیرعادی جزئیات (در پوستههایی که من با آنها تست کردهام) که فقط در Bash غیر POSIX و Zsh(تمام حالتهای شبیهسازی) پیش میآید، کار میکند.
پوسته باید تعریف نامهای تابع، ماورای آنها که به واسطه یک POSIX "Name" مجاز هستند را اجازه بدهد. این مطلب بلافاصله ksh93 و Bash در وضعیت POSIX و Dash و Posh ( Posh یک شاخه قدیمی pdksh است که دیگر پشتیبانی نمیشود)، و Busybox sh را رد میکند.
به طور نادرست توابعی مقرر کند که داخلیهای ویژه را پیش از داخلی خودش بارگذاری میکند. جستجو و اجرای فرمان را ببینید. mksh در این مرحله(به طور صحیح) ناموفق میشود، واقعاً دستور : داخلی را اجرا میکند. حتی اگر شما تابع را به طور موفقیت آمیز تعریف کنید، فراخوانی تابع غیر ممکن است. Bash در وضعیت غیرPOSIX و Zsh (حتی شبیهساز POSIX)مطابق این ضوابط هستند.
$ bash -c 'enable -d :; type -p :'
bash: line 0: enable: :: not dynamically loaded
$ ksh -c 'builtin -d :; whence -v :'
ksh: whence: :: not found
احتمالاً یک باگ: -d **.هر داخلی تعریف شده را حذف میکند. **داخلی ویژه نمیتواند حذف بشوددر هر صورت، نامربوط است زیرا ksh93 قبلاً در مرحله اول ناموفق شده است. اکنون شما فقط یک داخلی غیرقابل دسترس دارید.
بنابراین به طور خلاصه، این forkbomb خیلی جالب نیست. اساساً تعریف متعارفی است که به طور مبتذل توسط تخصیص یک نام ناشناس که تقریباً در هر جایی ناموفق است، گیج کننده گردیده است. بنا به ادعای فرضی کنایهدار نویسنده اصلی، شخص میتواند در هر ترمینال یونیکس تایپ کند:
:(){ :|:& };:
پرسش و پاسخ 59 (آخرین ویرایش 2013-01-08 18:58:36 توسط GreyCat)
به طور اساسی پاسخ خیر است....
در حالیکه bash مشکلاتی به زیادی پوستههای قدیمیتر با آنها ندارد، باز هم نمیتواند دادههای باینری اختیاری را پردازش نماید، و به طور اخص، متغیرهای پوسته 100% باینری خالص نیستند، بنابراین نمیتوانید فایلهای باینری را در آنها ذخیره کنید.
شما میتوانید دادههای اسکی کُدگذاری شده یونیکس به یونیکس(uuencoded) را به این طریق در متغییر قرار دهید:
var=$(uuencode /bin/ls ls)
cd /somewhere/else
uudecode <<<"$var" # نقلقولها را فراموش نکنید
توجه: تفاوت سترگی میان uuencode یا uudecode گنو و یونیکس وجود دارد. با uudecode یونیکس، شما نمیتوانید فایل خروجی تعیین کنید، همواره از نام فایل کدگذاری شده در داده اسکی استفاده میکند. من مثال قبلی را اصلاح کردهام به طوری که در سیستمهای یونیکس کار میکند. اگر شما تغییرات بیشتری ایجاد میکنید، لطفاً رویهگرایی گنو (GNUisms) را به کار نبرید. متشکرم. --GreyCat
یک نمونه که چنین موردی ممکن است گاهی در آن سودمند باشد، ذخیره کردن نقشهبیتیهای کوچک موقتی، در هنگام کار کردن با netpbm است... در اینجا من به یک pnmnoraw زیرنویس 1 اضافی در لوله متوسل شدم که باعث ایجاد فایلهای اسکی بزرگتر میشود و bash با ذخیره آنها مشکلی ندارد.
اگر احساس ماجراجویی دارید، این تجربه را ملاحظه نمایید:
# bindec.bash, سعی میکند دادههای باینری را به اسکی دسیمال رمزگشایی کند
IFS=
while read -n1 x ;do
case "$x" in
'') echo empty ;;
# ۲۵۶ سطر تولید شده توسط سطر فرمان زیر را در اینجا درج کنید
# for x in $(seq 0 255) ;do echo " $'\\$(printf %o $x)') echo $x;;" ;done
esac
done
و سپس داده باینری را به آن لولهکشی کنید، شاید اینطور :
for x in $(seq 0 255) ;do echo -ne "\\$(printf %o $x)" ;done | bash bindec.bash | nl | less
این کد ایجاب میکند که کاراکتر 0 به طور کلی از قلم انداخته شود، زیرا ما نمیتوانیم آنرا با تولید کننده ورودی ایجاد کنیم، به راحتی برای خراب کردن اکثر فایلهای باینری که میخواهیم پردازش کنیم، کفایت میکند.
بلی، Bash به زبان C نوشته شده، و از معناشناسی این زبان برای مدیریت رشتهها -- شامل بایتهای NUL به عنوان حدفاصل رشتهها -- در متغیرهایش استفاده میکند. شما نمیتوانید به طور معقولی NUL را در متغیرهای Bash ذخیره نمایید. در حقیقت هرگز هم قرار نبوده به این صورت به کار برود. - GreyCat
توجه نمایید که این مطلب اشاره به نگهداری آنها در متغیرها دارد... انتقال دادهها بین برنامهها با استفاده از لولهها همیشه باینری بدون عیب است. فایلهای موقتی نیز به شرطی که موقع ایجاد آنها اقدامات احتیاطی متناسب انجام بشود، بیخطر هستند.
برای cat کردن فایل باینری تنها با دستورات داخلی bash موقعی که برنامه خارجی در دسترس نباشد(یکبار وقتی نام فایل /lib/libgcc_s.so.1 تغییر کرده بود، استفاده از این ترفند کارم را راه انداخت):
# باینری مطمئن bash فقط با دستورات داخلی cat شبیهسازی
IFS=
while read -d '' -r -n1 x ; do
case "$x" in
'') printf "\x00";;
*) printf "%s" "$x";;
esac
done
من ترجیح خواهم داد از cat استفاده کنم. همچنین، آن -n1 واقعاً لازم بود؟ -GreyCat
بدون -n1 شما باید برای کار کردن با دادههای بعد از آخرین \0 خیلی با احتیاط باشید، موردی مانند این [[ $x ]] && printf "%s" "%x" بعد از حلقه. من این را بررسی نکردهام که بدانم آیا کار میکند یا اینکه کافی هست. همچنین من نمیدانم اگر شما یک فایل بزرگ بدون هیچ \0 را بخوانید چه اتفاقی میافتد --pgas
پرسش و پاسخ 58 (آخرین ویرایش 2009-03-09 08:26:26 توسط pgas)
چنان که شخصی بخواهد این فایل را:
foo: entry1
bar: entry2
foo: entry3
baz: entry4
تبدیل کند به این
foo: entry1 entry3
bar: entry2
baz: entry4
دو روش ساده عمومی برای انجام این کار وجود دارد:
یک پیادهسازی اساسی از روش
old=xxx ; stuff=
(sort file ; echo xxx) | while read prefix line ; do
if [[ $prefix = $old ]] ; then
stuff="$stuff $line"
else
echo "$old: $stuff"
old="$prefix"
stuff=
fi
done
و یک پیادهسازی اساسی از روش
{
a[$1,++b[$1]] = $2;
}
END {
for (i in b) {
printf("%s", i);
for (j=1; j<=b[i]; j++) {
printf(" %s", a[i,j]);
}
print "";
}
}
نوشته شده به صورت یک سطر دستور مفصل در پوسته:
awk '{a[$1,++b[$1]]=$2} END {for (i in b) {printf("%s", i); for (j=1; j<=b[i]; j++) printf(" %s", a[i,j]); print ""}}' file
پرسش و پاسخ 57 آخرین ویرایش 2012-03-29 20:36:56 توسط ormaaj)
چون فرمان tar در اصل طوری طراحی شده بود که از دستگاههای نوار گردان مغناطیسی بخواند یا در آن بنویسد(کلمه tar از
یک گزینه وجود دارد که به tar میگوید آرشیو روی نوار مغناطیسی نیست، بلکه در یک فایل است: -f. این گزینه دقیقاً یک شناسه میپذیرد: نام فایل محتوی آرشیو. تمام نام فایلهای دیگر(در ادامه) به عنوان اعضاء آرشیو در نظر گرفته میشوند:
tar -x -f backup.tar myfile.txt
# ( IMHO یا(ترکیب دستوری عمومیتر
tar xf backup.tar myfile.txt
حال در اینجا یک اشتباه رایج هست -- تصور کنید دایرکتوری شامل فایلهای بایگانی زیر است و میخواهید همه را به یکباره استخراج کنید:
$ ls
backup1.tar backup2.tar backup3.tar
شاید فکر کنید با دستور tar xf *.tar انجام بدهید. بیایید ببینیم:
$ tar xf *.tar
tar: backup2.tar: Not found in archive
tar: backup3.tar: Not found in archive
tar: Error exit delayed from previous errors
جه اتفاقی رخ میدهد؟ پوسته *.tar شما را با فایلهای قابل انطباق تعویض میکند. در حقیقت شما نوشتهاید:
tar xf backup1.tar backup2.tar backup3.tar
و به طوری که فبلاً دیدیم، به معنی آنست که: «فایلهای backup2.tar و backup3.tar را از فایل بایگانی backup1.tar استخراج کن», که البته فقط موقعی موفق خواهد شد که چنین نام فایلهایی در فایل بایگانی ذخیره شده باشند.
راه حل نسبتاً آسان است: استخراج محتویات تمام آرشیوها به طور یکی یکی. چنانچه ما از شل یونیکس استفاده میکنیم و تنبل هستیم، با یک حلقه آن کار را انجام میدهیم:
for tarname in ./*.tar; do
tar xf "$tarname"
done
چه اتفاقی میافتد؟ حلقه for روی تمام نام فایلها که با *.tar منطبق میشوند تکرار میکند و tar xf را برای هر یک از آنها فرامیخواهد. به این طریق شما تمام فایلهای بایگانی را یک به یک استخراج میکنید و درست آن را به طور خودکار انجام میدهید.
دومین نوع آرشیو رایج در این روزها ZIP است. فرمان unzip برای استخراج محتویات از یک فایل ZIP است(چه کسی آن را پیشنهاد کرده است!). مشکل در اینجا خیلی مشابه است: unzip فقط یک گزینهِ تعیین کننده فایل ZIP را میپذیرد. بنابراین، آن را به همان روش حل کنید:
for zipfile in ./*.zip; do
unzip "$zipfile"
done
کافی نیست؟ Ok. گزینه دیگری همراه unzip وجود دارد: این گزینه میتواند الگوهای شل-مانند را برای تعیین نام فایلهای ZIP قبول کند. و برای پرهیز از تفسیر آن الگوها توسط پوسته، لازم است آنها را نقلقولی کنید. در این حالت خود unzip و
unzip "*.zip"
# :یا برای شفاف کردن آن که چه کار میکنیم
unzip \*.zip
(این ویژگی دستور unzip در اصل از خاستگاه آن به عنوان یک برنامه MS-DOS ناشی میگردد. مفسر فرمان MS-DOS بسطهای glob انجام نمیدهد، بنابراین هر برنامه MS-DOS باید قادر باشد فوق کاراکترها را به لیستی از نام فایلها بسط بدهد. این ویژگی در نگارش یونیکس باقی گذاشته شد، و به طوری که نمایش دادیم، گاهی اوقات میتواند مفید باشد.)
پرسش و پاسخ 56 ( 2011-02-11 19:26:45 توسط GreyCat)