برخی وسائل ممانعت دوجانبه را لازم داریم. یک روش، استفاده از "lock" است: هر تعداد از پردازشها میتوانند به طور همزمان برای بدست آوردن قفل تلاش نمایند، اما فقط یکی از آنها موفق میشود.
چگونه میتوانیم این کار را با استفاده از اسکریپتهای شل انجام بدهیم؟ بعضی افراد ایجاد یک فایل قفل، و کنترل وجود آنرا پیشنهاد میدهند:
# مثال قفل کردن -- اشتباه
lockfile=/tmp/myscript.lock
if [ -f "$lockfile" ]
then # قفل قبلاً تصرف شده
echo >&2 "cannot acquire lock, giving up: $lockfile"
exit 0
else # هیچ کس مالک قفل نیست
> "$lockfile" # ایجاد فایل قفل
#...ادامه اسکریپت
fiاین مثال کار نمیکند، زیرا این یک Race Condition میباشد: یک دریچه زمانی بین بررسی و ایجاد فایل در مدتی که سایر برنامهها ممکن است عمل کنند. فرض کنید دو پردازش در یک زمان در حال اجرای این کُد میباشند. هر دو وجود فایل قفل را بررسی میکنند، و هر دو نتیجه میگیرند که فایل وجود ندارد. حالا هر دو پردازش گمان میکنند که قفل را به دست آوردهاند -- ناکامی در شرف وقوع است. ما به یک روند بررسی و ایجاد اتمی(تجزیه ناپذیر) نیاز داریم ، و خوشبختانه یکی وجود دارد: mkdir، فرمان ایجاد یک دایرکتوری:
# مثال قفل کردن -- صحیح
# Bourne
lockdir=/tmp/myscript.lock
if mkdir "$lockdir"
then # دایرکتوری موجود نبود بنابراین با موفقیت ایجاد شد
echo >&2 "successfully acquired lock: $lockdir"
# ادامه اسکریپت
else
echo >&2 "cannot acquire lock, giving up on $lockdir"
exit 0
fiدر اینجا حتی وقتی دو پردازش در یک زمان فرمان mkdir را احضار کنند، فقط حداکثر یک پردازش میتواند موفق گردد. این حالت اتمی بررسی و ایجاد در سطح هسته سیستم عامل تأمین میشود.
به جای استفاده از mkdir همچنین میتوانستیم برنامه ایجاد پیوند نمادین را به کار ببریم، ln -s. سومین امکان، داشتن برنامه حذفِ فایل قفلِ از قبل موجود با rm است. قفل با ایجاد مجدد فایل هنگام خروج آزاد میشود.
توجه نمایید که نمیتوانیم از mkdir -p برای ایجاد خودکار اجزا غایب تشکیل دهنده مسیر استفاده کنیم: اگر دایرکتوری از قبل موجود باشد mkdir -p خطایی صادر نمیکند، بلکه خصیصهایست که ما برای اطمینان از ممانعت دوطرفه به آن استناد میکنیم(مترجم: مقصود، ویژگی اتمی بودن mkdir است).
حالا بیایید خودکار نمودن حذف قفل هنگام خاتمه یافتن اسکریپت را چاشنی این مثال کنیم:
# POSIX (maybe Bourne?)
lockdir=/tmp/myscript.lock
if mkdir "$lockdir"
then
echo >&2 "successfully acquired lock"
# حذف میشود lockdir ،وقتی اسکریپت به پایان میرسد یا موقع دریافت سیگنال
trap 'rm -rf "$lockdir"' 0 # حذف دایرکتوری موقع پایان یافتن اسکریپت
# به طور اختیاری فایلهای موقتی در این دایرکتوری ایجاد میشود، چون
# .آنها به طور خودکار حذف خواهند شد
tmpfile=$lockdir/filelist
else
echo >&2 "cannot acquire lock, giving up on $lockdir"
exit 0
fiاین مثال خیلی بهتر است. بازهم این مشکل وجود دارد که وقتی اسکریپت بدون یک سیگنال اخذ شده،(یا سیگنال 9، SIGKILL) خاتمه مییابد، قفل کهنهای میتواند باقی بماند، یا میتواند توسط کاربری(به طور تصادفی یا بدخواهانه) ایجاد بشود، اما این قدم خوبی در جهت ممانعت دوطرفه است. Charles Duffy یک مثال همکارانه دارد که ممکن است مشکل «قفل کهنه» را برطرف نماید .
اگر در لینوکس هستید، میتوانید از کاربرد flock(1) نیز بهرهمند شوید. flock(1) توصیفگر فایلی را به یک فایل قفل متصل میکند. روشهای چندگانهای برای استفاده از آن موجود است، یک امکان حل مشکلِ نمونههای چندتایی، این است:
exec 9>/path/to/lock/file
if ! flock -n 9 ; then
echo "another instance is running";
exit 1
fi
# اکنون این تحت قفل تا بسته شدن 9 اجرا میشود
# (وقتی اسکریپت تمام میشود 9 به طور خودکار بسته میشود)
flock همچنین میتواند فقط قسمتی از اسکریپت شما را محافظت کند، برای اطلاعات بیشتر صفحه manرا ببینید.
من معتقدم استفاده از if (set -C; >$lockfile); then ... کاملاً مطمئن است، اگر که مطمئنتر نباشد. منبع Bash از open(filename, flags|O_EXCL, mode); استفاده میکند که تقریباً در تمام سکوها تجزیهناپذیر خواهد بود(به استثنای برخی نگارشهای NFS که در آنها mkdir هم نمیتواند اتمی باشد). من نه مسیر نشانههای متغیر را دنبال کردهام، که باید محتوی O_CREAT باشند، نه در پوستههای دیگر رسیدگی نمودهام. من استفاده از این را تا موقعی که شخص دیگری ادعای مرا پشتیبانی نکند، پیشنهاد نمیکنم. --Andy753421
شما اطمینان دارید که mkdir با اتمی شدن روی NFS مشکل دارد؟ من گمان میکنم فقط تحت تأثیر بازشدن واقع شده، اما واقعاً مطمئن نیستم. -- BeJonas 2008-07-24 01:22:59
برای مباحثه بیشتر در باره این موضوعات، مدیریت پردازش را ببینید.
پرسش و پاسخ 45 (آخرین ویرایش 2013-01-11 20:08:22 توسط GreyCat)
آسانترین روش افزودن نوار پیشرفت به اسکریپت خودتان، استفاده از dialog --gauge است. در اینجا مثالی آوردهایم که متکی به ویژگیهای BASH میباشد:
# Bash
# .در شاخه جاری را پردازش میکند *.zip تمام فایلهای
files=(*.zip)
dialog --gauge "Working..." 20 75 < <(
n=${#files[*]}; i=0
for f in "${files[@]}"; do
# قرار بدهید "sleep 1" را قرار بدهید، برای تست "$f" در اینجا دستورات پردازش
echo $((100*(++i)/n))
done
)
این هم توضیح آن که چکار میکند:
یک آرایه به نام files با تمام فایلهایی که میخواهیم پردازش کنیم، دارای عضو میشوند.
dialog فراخوانی میشود، و ورودی آن به جایگزینی پردازش تغییر مسیر داده میشود. ( لوله نیز میتوانست در اینجا به کار برود، ما میبایست به سادگی فرمان dialog و حلقه را برعکس مینمودیم.)
هر نوبت که فایلی پردازش میشود، شمارشگر(i) را افزایش میدهد، و درصد تکمیل را در خروجی مینویسد.
برای مثالهای بیشتری از کاربرد dialog، پرسش و پاسخ شماره 40 را ببینید.
نوار پیشرفت سادهای نیز میتواند بدون استفاده از dialog برنامه ریزی بشود. رویکردهای متفاوت زیادی برای این مورد وجود دارد که بستگی به نوع نمایشی دارد که شما در جستجوی آن میباشید.
یک رویکرد سنتی چرخنده است که یک پاره خط در حال دوران را برای دلالت بر مشغول بودن نمایش میدهد. این در اصل میزان پیشرفت نیست چون اطلاعاتی در مورد آنکه چطور برنامه به تکمیل شدن نزدیک میشود، ارائه نمیکند.
مرحله بعدی نمایش یک مقدار عددی بدون حرکت در صفحه نمایش است. استفاده از سطر نورد برای حرکت نشانگر جهت تبدیل شدن به یک سطر(در ترمینال گرافیکی، نه یک دورنگار...) و ننوشتن سطر جدید تا به پایان رسیدن پردازش:
i=0 while ((i < 100)); do printf "\r%3d%% complete" $i ((i += RANDOM%5+2)) # را از جایی معنیدار دریافت میکنیم i البته در زندگی واقعی، ما sleep 1 done echo
نکته در اینجا تعیین کننده قالب %3d در printf است. استفاده از فیلد با عرض ثابت برای نمایش اعداد اهمیت دارد، مخصوصاً اگر شمارش معکوس انجام شود(اول 10 و بعد 9 نمایش داده شود). البته ما در اینجا شمارش رو به بالا انجام دادیم، اما به طور عمومی همیشه به این حالت نیست. اگر فیلد با عرض ثابت مطلوب نیست، آنوقت چاپ یک گروه فاصله در انتها میتواند به زدودن هر گونه درهمبرهمی با سطرهای قبلی کمک کند.
اگر بجای یک عدد، یک نوار واقعی مورد نظر باشد، آنوقت شاید شخصی با استفاده از کاراکترهای اسکی آن را رسم کند:
bar="=================================================="
barlength=${#bar}
i=0
while ((i < 100)); do
# .تعداد قطعات نوار برای رسم کردن
n=$((i*barlength / 100))
printf "\r[%-${barlength}s]" "${bar:0:n}"
((i += RANDOM%5+2))
# را از جایی معنیدار دریافت میکنیم i البته در زندگی واقعی، ما
sleep 1
done
echo
بدیهی است هر کس میتواند نواری با طول متفاوت، یا ترکیبی ازمجموعه کاراکترهای متفاوت را انتخاب کند، به عنوان مثال، میتوانید یک نوار پیشروی رنگی داشته باشید
files=(*)
width=${COLUMNS-$(tput cols)}
rev=$(tput rev)
n=${#files[*]}
i=0
printf "$(tput setab 0)%${width}s\r"
for f in "${files[@]}"; do
# قرار بدهید "sleep 1" را قرار بدهید، برای تست "$f" در اینجا دستورات پردازش
printf "$rev%$((width*++i/n))s\r" " "
done
tput sgr0
echo
نمیتوانید با cp(1) نمایشگر پیشروی به دست آورید، اما شما میتوانید :
شاید بخواهید از pv(1) استفاده کنید چون برای بسیاری از سیستمها به صورت بستهبندی شده موجود است. در این حالت، اگر تابع یا اسکریپتی برای پوشاندن آن ایجاد نمایید، مناسب است.
برای مثال:
pv "$1" > "$2/${1##*/}"
این فاقد کنترل خطا میباشد و از انتقال فایلها پشتیبانی میکند.
همچنین میتوانید از rsync استفاده کنید:
rsync -avx --progress --stats "$1" "$2"
لطفاً توجه نمایید هر دفعه که rsync وارد یک دایرکتوری میشود و فایلهای کمتر یا بیشتر از مورد انتظارش پیدا میکند، تعداد کل فایلها میتواند تغییر نماید، اما حداقل بیش از cp مطلع است. پیشرفت Rsync برای انتقالهای بزرگ با تعداد فایل کم مناسب است.
پرسش و پاسخ 44 (آخرین ویرایش 2010-06-25 20:12:14 توسط MatthiasPopp)
در بسیاری از نگارشهای crontab، با علامت (%) به طور ویژهای رفتار میشود، و بنابراین باید با کاراکتر گریز \ پوشش داده شود:
0 0 * * * some command > /var/log/mylog.`date +\%Y\%m\%d`
برای توضیحات بیشتر مستندات سیستم خود را ببینید(crontab(5) یا crontab(1)). توجه: در سیستمهایی که راهنمای crontab را به دو قسمت تقسیم میکنند، ممکن است شما برای خواندن بخش مورد نیاز خود لازم باشد man 5 crontab یا man -s 5 crontab را تایپ کنید.
پرسش و پاسخ 43 (آخرین ویرایش 2010-06-25 20:11:42 توسط MatthiasPopp)
فرمان kill برای ارسال سیگنالها به پردازش در حال اجرا به کار میرود. به عنوان یک عمل راحت، سیگنال "0" که موجود نیست میتواند برای پی بردن به در حال اجرا بودن یک پردازش به کار برود:
# Bourne
myprog & # برنامه را در پس زمینه آغاز میکند
daemonpid=$! # ...و شماره پردازش آن را ذخیره میکند
while sleep 60
do
if kill -0 $daemonpid # آیا پردازش هنوز زنده است؟
then
echo >&2 "OK - process is still running"
else
echo >&2 "ERROR - process $daemonpid is no longer running!"
break
fi
done
این یکی از آن پرسشهایی است که معمولاً مسئله عمیقتری را پوشش میدهد. نادر است که شخصی بخواهد بداند آیا پردازشی هنوز واقعاً در حال اجرا میباشد برای اینکه چراغ سبز یا قرمزی را برای متصدی روشن کند.
بیشتر اوقات، تقریباً انگیزهای فراتر وجود دارد، ازقبیل تمایل به اطمینان از آنکه آیا سرویسی که به طور مکرر عامل از کار افتادن سیستم شناخته شده، بازهم در حال اجرا میباشد. اگر این حالت است، بهترین راهکار تصحیح برنامه یا پیکربندی آنست به طوریکه از کارافتادن متوقف شود. اگر نمیتوانید این کار را انجام بدهید، پس فقط وقتی میمیرد restart کنید:
# POSIX while true do myprog && break sleep 1 done
این قطعه کُد برنامه myprog را اگر با کُد وضعیت دیگری غیر از صفر خاتمه یابد(نشان دهنده وجود یک اشتباه)، شروع مجدد (restart) میکند. اگر کُد وضعیت صفر است(به طور موفقیت آمیز تمام شود) حلقه پایان مییابد. (اگر پردازش شما سقوط میکند اما کد وضعیت صفر نیز برمیگرداند، پس کُد را طبق آن تنظیم کنید.) توجه نمایید که myprog باید در پیش زمینه اجرا شود. اگر به طور خودکار خودش را به حالت daemon میبرد، شما مست هستید.
برای مباحثه بسیار بهتر در باره این مسائل مدیریت پردازش یا پرسش و پاسخ شماره 33 را ببینید.
پرسش و پاسخ 42 (آخرین ویرایش 2012-10-27 10:38:13 توسط a88-114-128-29)
در BASH:
# Bash if [[ $foo = *bar* ]]
مورد فوق کمابیش در تمام نگارشهای Bash کار میکند. Bash نگارش 3 (و بالاتر) همچنین عبارتهای منظم را هم مجاز میداند:
# Bash my_re='ab*c' if [[ $foo =~ $my_re ]] # bash 3, matches abbbbcde, or ac, etc.
برای اشارات بیشتر در مورد دستکاری رشتهها در Bash، پرسش و پاسخ شماره 100 را ببینید.
اگر شما به جای Bash در BourneShell برنامه نویسی میکنید، ترکیب دستوری قابل حملتر(اما نا زیباتری) وجود دارد:
# Bourne
case "$foo" in
*bar*) .... ;;
esac
case به شما اجازه میدهد متغیرها را در مقابل الگوهای globbingشکل (به انضمام globهای توسعه یافته) در صورتی که پوسته شما آنها را ارائه میکند،مطابقت دهید. اگر شما به روش قابل حمل برای مطابقت متغیرها در مقابل عبارات منظم نیاز دارید، از grep یا egrep استفاده کنید.
# Bourne if echo "$foo" | grep bar >/dev/null 2>&1; then ...
پرسش وپاسخ 41 (آخرین ویرایش 2010-11-29 22:24:01 توسط GreyCat)