چاره کار استفاده از case میباشد:
# Bourne
case "$var" in
foo|bar|more) ... ;;
esac
در Bash و ksh همین کار را، globهای توسعه یافته نیز میتوانند در داخل فرمان [[ انجام بدهند:
# bash/ksh
${BASH_VERSION+shopt -s extglob}
if [[ $var == @(foo|bar|more) ]]; then
...
fi
به طور جایگزین، برای کنترل هر یک از اقلام لیست الگوها، میتوانید یک حلقه بر روی آنها به کار ببرید.
# bash/ksh93/zsh (w/ emulate ksh)
# usage: pmatch string pattern [ pattern ... ]
function pmatch {
${1+typeset x=}"${1-false}" &&
while command shift; do
[[ $x == $1 ]] && return
done 2>/dev/null
return 1
}
var='foo bar'
if pmatch "$var" foo bar baz foo\* blarg; then
: ...
fi
ksh93 برای اجتماع(
# ksh93 فقط در پوسته
[[ $var == @(foo&bar&more) ]] && ...
تنها برای پوستههایی که (الگوهای extglob) زیرمجموعه ksh88 را پشتیبانی میکنند، میتوانید استفاده منطقی از عملگر نفی الگوی فرعی را مطابق قوانین DeMorgan استفاده کنید. زیرنویس 1
# bash/ksh88/etc...
${BASH_VERSION+shopt -s extglob}
[[ $var == !(!(foo)|!(bar)|!(more)) ]] && ...
اما این مورد کاملا غیر شفاف است و خیلی کوتاهتر از نوشتن عبارتهای جداگانه برای هر الگو نیز نمیباشد.
پرسش و پاسخ 66 (آخرین ویرایش 2013-01-14 22:03:43 توسط ormaaj)
نفی (A و B) معادل است با (نفی A) یا (نفی B) و به بیان دیگر !(A & B) معادل (!A) | (!B) است
و همچنین:
نفی (A یا B) معادل است با (نفی A) و (نفی B) به عبارتی !(A | B) همان (!A) & (!B) است
کُد زیر را برای انتظار تا فشردن کلید اینتر توسط کاربر به کار ببرید:
# Bash read -p "Press [enter] to continue..." # Bourne echo "Press [enter] to continue..." read junk
یا استفاده از مورد زیر برای منتظر ماندن تا موقعی که کاربر برای ادامه یک ضربه کلید بزند:
# Bash read -rsn 1 -p "Press any key to continue..."
گاهی اوقات در حالیکه شما از قبل به علت اینکه (به عنوان مثال) در حال استفاده از لولهای برای تغذیه اسکریپت خود میباشید، در حال خواندن از ورودی استاندارد هستید، اما لازم است تا فشردن کلیدی توسط کاربر برای ادامه منتظر بمانید. چگونه به read میگویید که از صفحه کلید بخواند؟ در اینجا یونیکس به طور قابل انعطافی سودمند است، شما میتوانید "< /dev/tty" را اضافه کنید
# Bash read -rsn 1 -p "Press any key to continue..." < /dev/tty
اگر میخواهید مهلت معین برای انتظار قرار بدهید، از گزینه -t برای read استفاده نمایید:
# Bash printf 'WARNING: You are about to do something stupid.\n' printf 'Press a key within 5 seconds to cancel.' if ! read -rsn 1 -t 5 then something_stupid fi
اگر فقط میخواهید مدتی زمانی را بدون اعتنا به ورودی کاربر، متوقف گردد، sleep را به کار ببرید:
echo "The script is tired. Please wait a minute." sleep 60
اگر یک شمارش معکوس تفننی برای زمان دار نمودن read میخواهید:
# Bash
# .این تابع عدد چند رقمی را نمیتواند مدیریت کند
countdown() {
local i
printf %s "$1"
sleep 1
for ((i=$1-1; i>=1; i--)); do
printf '\b%d' "$i"
sleep 1
done
}
printf 'Warning!!\n'
printf 'Five seconds to cancel: '
countdown 5 & pid=$!
if ! read -s -n 1 -t 5; then
printf '\nboom\n'
else
kill "$pid"; printf '\nphew\n'fi
(اگر این کُد را در پوسته محاورهای به کار ببرید، در اثر سیستم کنترل job موقعی که پردازش فرزند تولید میشود و موقعی که کُشته میشود، شاهد اختلالی خواهید بود، اما در اسکریپت چنین اختلالی وجود نخواهد داشت.)
پرسش و پاسخ 65 (آخرین ویرایش 2013-04-08 12:28:58 توسط geirha)
شاید هیچ کس جواب را نداند( یا افرادی که میدانند مشغول هستند). شاید شما توضیح کافی در باره مشکل ندادهاید، یا شما مشکل را به طور شفاف بیان نکردهاید. شاید سؤالی که پرسیدهاید در این FAQ، یا در تلههای Bash، یا در راهنمای BashGuide، پاسخ داده شده است.
این یک مورد مهم است که: فقط یک URL ارسال نکنید که بگویید «اسکریپت من اینجاست، آن را اصلاح کنید!» اگر قطعه کوچکی از کد دارید که آنرا نمیفهمید به عنوان آخرین چاره، کد را ارسال کنید. در عوض تشریح کنید که سعی در انجام چه کاری دارید.
اسکریپتنویسی پوسته عمدتاً مجموعهای از پاسخها و ترفندهایی است که نمیتوانند خیلی خوب تعمیم داده شوند. پاسخ بهینه به یک مشکل میتواند با پاسخ مطلوب برای مشکل دیگری که مشابه آن به نظر میرسد کاملاً متفاوت باشد، بنابراین بینهایت مهم است مشکلی را که میخواهید حل کنیم، به طور دقیق به ما بگویید.
علاوه براین، اگر شما تلاش نمودهاید که خودتان مشکل را برطرف نمایید، واقعاً احتمال بسیاری وجود دارد با استفاده از تکنیکی که کار نمیکند(یا حداقل برای آن مشکل خاص کار نمیکند) تقلا کرده باشید. هر کدی که از قبل دارید احتمالاً میباید دور ریخته شود. ارسال کُد خودتان که کار نمیکند به عنوان جایگزین توصیف مشکلی که میخواهید حل شود معمولاً اتلاف وقت است، و تقریباً همیشه برخورنده است.
برای توصیههای عمومیتر آداب معاشرت اینترنت را ببینید. سعی کنید از XyProblem مفتضح پرهیز نمایید.
همچنین:
#bash aphorism 1 (کلمات قصار شماره ۱ bash): توصیف اول پرسش کننده در مورد پرسش یا مشکل گمراه کننده خواهد بود.
نتیجه گیری #bash 1.1: توصیف ثانوی پرسش کننده در مورد پرسش یا مشکل نیز گمراه کننده خواهد بود.
#bash aphorism 2 (کلمات قصار شماره ۲ bash): پرسش کننده آنقدر پرسش اولیه خود را تغییر خواهد داد تا کمک کننده در کانال را دیوانه کند.
کلمات قصار ارائه شده در اینجا خنده دار میباشد، اما یک احساس واقعگرایانه در آنها نهفته است. افزون بر این، چندین مورد پیشنهاد شده است، اما فقط آنها که در بالا نشان داده شد به طور دست نخورده باقی مانده است. دیگران عبارتند از:
پرسش و پاسخ 64 (آخرین ویرایش 2011-05-12 19:54:44 توسط GreyCat)
کُد زیر آنچه را انتظار دارید، انجام نخواهد داد:
ssh me@remotehost 'sleep 120 &' # Client hangs for 120 seconds
این ویژگی OpenSSH میباشد. سرویسگیرنده تا وقتی که ترمینال راه دور هنوز در حال استفاده است، ارتباط را قطع نمیکند -- و در حالت sleep 120 &، بازهم stdout و stderr به ترمینال متصل هستند.
پاسخ بیدرنگ به این پرسش شما -- «چگونه میتوانم ارتباط سرویس گیرنده را قطع کنم به طوری که اعلان پوسته را پس بگیرم؟» -- kill کردن سرویس گیرنده ssh است. البته میتوانید این کار را با فرمانهای kill یا pkill، یا با ارسال سیگنال INT (معمولاً Ctrl-C ) برای یک نشست ssh غیرمحاورهای(مانند بالا)، یا برای پوسته محاورهای راه دور با فشردن کلیدهای <Enter><~><.> (اینتر، مد، نقطه) در پنجره ترمینال سرویس گیرنده، انجام بدهید.
چارهجویی طولانیتر این مورد، تأمین نمودن آنست که در طرف راه دور تمام توصیفگرهای فایل به فایل ثبت وقایع (یا به /dev/null) تغییر مسیر داده شوند:
ssh me@remotehost 'sleep 120 >/dev/null 2>&1 &' # فوراً باز میگردد
همچنین این مورد در برخی سیستمهای یونیکسی قدیمی سرویسهای کمکی (daemon) را دوباره راهاندازی میکند.
ssh root@hp-ux-box # پوسته محاورهای ... # روشن میکند که مشکل در سیستمفایل شبکه است /sbin/init.d/nfs.client stop # با این اسکریپت مدیریت میشود و autofs /sbin/init.d/nfs.client start # قبول است (برخلاف لینوکس ) HP-UX کشتن آن در exit # آنرا بکُشید Enter ~ . سرویس گیرنده هنگ میکند با استفاده از
لطفاً توجه نمایید که تحت SSH اجازه دادن به root برای ایجاد فایل ثبت رخداد، از نظر امنیتی، عادت بسیار بدی است. اگر شما باید این کار را انجام بدهید، پس یک اسکریپت منفرد ایجاد کنید که تمام دستورهای مورد نظر شما را بدون هیچ گزینه خط فرمان اجرا کند، و سپس فایل sudoers را طوری پیکربندی کنید که به یک کاربر مناسب بدون نیاز به کلمه عبور اجازه اجرای اسکریپت اشاره شده را بدهد. این کار متضمن آن خواهد بود که شما آگاه باشید چه فرمانهایی لازم است به طور مرتب اجرا بشوند و در صورتی که حساب کاربری مقرر در خطر فاش شدن قرار گیرد، خسارتی که میتواند وارد شود، محدود میگردد.
اسکریپت /sbin/init.d/nfs.client میراث یونیکس، سرویسهای کمکی را در پسزمینه اجرا میکند اما اجازه میدهد stdout و stderr آنها به ترمینال متصل شوند(و آنها به طور کامل self-daemonize نیستند). راه حل آن یا تعمیر اسکریپت ناقص init فروشنده یونیکس، یاکُشتن پردازش سرویس گیرنده ssh پس از وقوع این مورد است. مؤلف این گفتار از رویکرد اخیر استفاده میکند.
پرسش و پاسخ 63 (آخرین ویرایش 2011-08-05 18:02:00 توسط GreyCat)
به نظر نمیرسد هیچ فرمان منفردی وجود داشته باشد که واقعاً در همه جا کار بکند. 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 ایجاد کنید.
# 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) پیادهسازی میکند. اما این کار دو مشکل دارد:
پرسش و پاسخ 62 (آخرین ویرایش 2013-01-11 04:00:34 توسط 82)