اگر پرسش واقعی شما «چطور میتوانم بررسی کنم که آیا یکی از پارامترهایم -v است؟» میباشد، پس لطفاً پرسش و پاسخ شماره 35 را ملاحظه کنید.در غیر اینصورت، خواندن را ادامه دهید....
اول از همه، بیایید اصطلاحات را مرتب کنیم. Bash مفهوم لیستها یا مجموعهها یا چیزی از این قبیل ندارد. Bash رشتهها و آرایهها را دارد. رشتهها لیستی از کاراکترها میباشند، آرایهها لیستی از رشتهها هستند.
در یک آرایه سنتی معین، تنها روش صحیحِ انجام این کار، اجرای یک حلقه روی تمام عناصر آرایه و کنترل آنها برای عنصر مورد جستجوی شما میباشد. فرض کنیم آنچه در جستجوی آن هستیم bar است و لیست ما در آرایه foo قرار دارد:
# Bash for element in "${foo[@]}"; do [[ $element = $bar ]] && echo "Found $bar." done
اگر نیاز دارید این کار را چندین مرتبه در اسکریپت خود انجام بدهید، ممکن است بخواهید منطق آنرا در یک تابع به کار ببرید:
# Bash isIn() { local pattern="$1" element shift for element do [[ $element = $pattern ]] && return 0 done return 1 } if isIn "jacob" "${names[@]}" then echo "Jacob is on the list." fi
یا، اگر میخواهید تابع شما شماره شاخص عضوی که عنصر در آن پیدا شده را بازگرداند:
# Bash 3.0 or higher indexOf() { local pattern=$1 local index list shift list=("$@") for index in "${!list[@]}" do [[ ${list[index]} = $pattern ]] && { echo $index return 0 } done echo -1 return 1 } if index=$(indexOf "jacob" "${names[@]}") then echo "Jacob is the ${index}th on the list." else echo "Jacob is not on the list." fi
اگر لیست شما در یک رشته محصور شده، و به دلیل تقریباً نابخردانه، بی پروایی نسبت به هشدارها را انتخاب کردهاید، میتوانید از کُد پایین برای جستجوی کلمات در سرتاسر رشته، استفاده کنید. (تنها عذر موجه برای این مورد میتواند این باشد که شما در پوسته Bourne که آرایه ندارد، گیر افتاده باشید.)
# Bourne set -f for element in $foo; do if test x"$element" = x"$bar"; then echo "Found $bar." fi done set +f
در اینجا کلمه به عنوان هر زیر رشتهای که با فضای سفید(یا به طور دقیقتر کاراکترهای فعلی در متغیر IFS) مرزبندی شده تعریف میشود. دستور set -f از بسط glob در کلمات داخل لیست پیشگیری میکند. فعال کردن مجدد بسط glob با (set +f) اختیاری است.
اگر شما در حال کار با bash نسخه 4 یا ksh93 میباشید، به آرایههای انجمنی دسترسی دارید. اینها به شما اجازه میدهند مشکل را تجدید سازمان بدهید -- به جای ساختن لیستی از کلمات که مجاز هستند، شما میتوانید یک آرایه انجمنی بسازید که در آن کلیدها کلماتی هستند که شما میخواهید در نظر بگیرید. مقادیر آنها -- بسته به طبیعت مشکل --میتوانند با معنی باشند یا نباشند.
# Bash 4 declare -A good for word in "goodword1" "goodword2" ...; do good["$word"]=1 done # مجاز است $foo کنترل اینکه آیا if ((${good[$foo]})); then ...
این هم یک روش دستیابی غیر موجه که شما نباید از آن استفاده کنید، اما به خاطر تکمیل مطلب ارائه میشود:
# Bash if [[ " $foo " = *" $bar "* ]]; then echo "Found $bar." fi
( مشکل در اینجا آنست که فرض میشود space میتواند به عنوان جداکننده بین کلمات استفاده شود. عناصر شما ممکن است شامل فاصله باشند، که این روش را ناموفق میسازد!)
همان روش، برای پوستههای Bourne:
# Bourne case " $foo " in *" $bar "*) echo "Found $bar.";; esac
همچنین میتوانید از glob توسعه یافته با printf برای جستجوی کلمه در یک آرایه استفاده کنید. من این را به اندازه کافی تست نکردهام، بنابراین ممکن است در بعضی موقعیتها ناموفق شود --sn18
# Bash shopt -s extglob # glob تبدیل آرایه به printf -v glob '%q|' "${array[@]}" glob=${glob%|} [[ $word = @($glob) ]] && echo "Found $word"
این کُد موقعی که یک عنصر آرایه شامل کاراکتر
printf %q نیز کاراکتر
grep گنو یک ویژگی \b دارد که که بنا به گفته برخی بر لبههای کلمات مطابقت میکند (مرزهای کلمه). ممکن است شخصی سعی کند با کاربرد آن، رویکرد کوتاهتر استفاده شده فوق را بازآزمایی نماید، اما این کار توأم با خطر است:
# یکی از پارامترهای مکانی است؟ 'foo' آیا egrep '\bfoo\b' <<<"$@" >/dev/null && echo yes # یکی از پارامترهای مکانی است؟ '-v' این جایی است که شکست میخورد: آیا egrep '\b-v\b' <<<"$@" >/dev/null && echo yes # را به صورت یک کلمه جداگانه میبیند "v" , \b متأسفانه # را به چه جهنمی میفرستد "-" کسی نمیداند # هست؟ 'array' در آرایه "someword" آیا کلمه egrep '\bsomeword\b' <<<"${array[@]}" # باشد نمیتوانید از این استفاده کنید '-v' همان someword بدیهی است اگر کلمه
چون این ویژگی grep گنو، هم غیر قابل حمل است و هم به طور ناکافی تعریف شده، پیشنهاد میکنیم از آن استفاده نکنید. این مورد واقعاً در اینجا به خاطر تکمیل کردن مبحث، اشاره میشود.
این شیوه تلاش میکند رشته مورد نظر را با تمام محتویات آرایه مقایسه نماید. به طور بالقوه میتواند بسیار مؤثر باشد، اما بستگی به جداکننده دارد که نباید در کمیت مورد جستجو یا در آرایه موجود باشد. در اینجا ما از
# usage: if has "element" list of words; then ...; fi has() { local IFS=$'\a' t="$1" shift [[ $'\a'"$*"$'\a' == *$'\a'$t$'\a'* ]] }
در ksh93t یا بعد از آن شاید کسی با استفاده از دستور داخلی enum انواع، متغیرها، یا ثابتهای enum ایجاد نماید. اینها به طور مشابه با enumهای C کار میکنند(و خصیصه معادل در سایر زبانها). اینها میتوانند برای محدود نمودن مقادیری که شاید به متغیری اختصاص داده شود، به کار بروند، به طوریکه از لزوم یک بررسی سنگین در هر نوبتی که یک متغیر آرایهای تنظیم یا ارجاع میشود، اجتناب گردد. مانند انواع تولید شده با استفاده از typeset -T، نتیجه یک فرمان enum، فرمان تعریف جدیدی است که میتواند برای معرفی اقلام آن نوع به کار برود.
# ksh93 $ enum colors=(red green blue) $ colors foo=green $ foo=yellow ksh: foo: invalid value yellowمترجم: در کُد فوق، به این دلیل خطا صادر میشود که ما متغیر را چنان تعریف کردهایم که مقادیر قابل تخصیص به آن، به سه رنگ نامبرده محدود گردیده است
typeset -a همچنین میتواند در ترکیب با یک نوع enum برای پذیرفتن ثابتهای enum به عنوان زیرنویس به کار برود.
# ksh93 $ typeset -a [colors] bar $ bar[blue]=test1 $ typeset -p bar typeset -a [colors] bar=([blue]=test) $ bar[orange]=test ksh: colors: invalid value orange
برای مثالهای بیشتر src/cmd/ksh93/tests/enum.sh در منبع AST را ببینید.
پرسش و پاسخ 46 (آخرین ویرایش 2012-07-24 04:36:07 توسط ormaaj)
برخی وسائل ممانعت دوجانبه را لازم داریم. یک روش، استفاده از "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)