در اکثر پوستهها، هر یک از فرمانهای یک لوله در پوسته فرعی جداگانهای اجرا میگردد. نه در پوسته کاری:
# فعال شده کار میکند lastpipe نگارش 4.2 با bash یا ksh88/ksh93 فقط در # در سایر پوستهها صفر را چاپ میکند linecnt=0 printf '%s\n' foo bar | while read -r line do linecnt=$((linecnt+1)) done echo "total number of lines: $linecnt"
دلیل این رفتار تعجبآور پنهانی، آنطور که در بالا وصف شد، آنست که هر پوسته فرعی یک محیط و زمینه متغیر جدیدی برقرار میکند. حلقه while فوق در پوسته فرعی جدیدی با نسخه خودش از متغیر linecnt اجرا میشود، که از پوسته والدش که آنرا با مقدار اولیه '0' ایجاد نموده اخذ کرده است. سپس این نسخه برای شمارش به کار میرود . وقتی حلقه while خاتمه مییابد، نسخه پوسته فرعی دور انداخته میشود، و مقدار اصلی متغیر linecnt پوسته والد(که مقدارش تغییر نکرده) در فرمان echo به کار میرود.
پوستههای متفاوت در این وضعیت رفتار متفاوتی نشان میدهند:
پوسته Bourne موقعی که ورودی یا خروجی هر چه(حلقهها، case و غیره) به غیر از یک فرمان ساده , با استفاده از عملگر تغییر مسیر(< و >) یا لوله، تغییر مسیر داده میشود، یک پوسته فرعی ایجاد میکند.
BASH فقط اگر حلقه قسمتی از لوله باشد، یک پردازش جدید ایجاد میکند.
پوسته Korn فقط اگر حلقه قسمتی از یک لوله باشد، اما آخرین بخش آن نباشد پوسته فرعی ایجاد میکند. مثال read در بالا به طور واقعی در ksh88 و ksh93 کار میکند! (اما در mksh خیر)
POSIX رفتار bash را تصریح میکند، اما به عنوان یک توسعه اجازه میدهد هر قسمت یا تمام لوله بدون پوسته فرعی اجرا شود(به این ترتیب رفتار شل Korn را به خوبی مجاز میکند).
موارد بیشتر معیوب :
# Bash 4 # بدون حلقه نیز مشکل رخ میدهد printf '%s\n' foo bar | mapfile -t line printf 'total number of lines: %s\n' "${#line[@]}" # صفر را چاپ میکند
f() { if [[ -t 0 ]]; then echo "$1" else read -r var fi }; f 'hello' | f echo "$var" # هیچ چاپ نمیکند
دوباره، در هر دو حالت لوله باعث میشود read یا بعضی فرمانها مشمول اجرا در پوسته فرعی شوند، بنابراین اثر آن هرگز در پردازش والد مشاهده نمیشود.
باید تأکید بشود که موضوع مختص حلقهها نیست. خصوصیت عمومی تمام لولهها میباشد، گرچه حلقه "while/read" میتواند مثال استانداردی ملاحظه شود که وقتی افراد توضیحات صفحات man یا help دستور داخلی read را میخوانند و توجه داده میشوند که این دستور داده را از stdin میپذیرد، بارها و بارها ظاهر میگردد. آنها ممکن است به یاد بیاورند که داده تغییر مسیر یافته به درون دستور مرکب در سرتاسر آن دستور در دسترس است، اما درک نمیکنند چراتمام جایگزینی پردازشها و تغییر مسیرهای ظریفی که در سرتاسر محلهایی مانند FAQ #1 به کار میاندازند، ضروری هستند. طبعاً آنها به قرار دادن مواردی به طور مستقیم در لولهها اقدام میکنند و ازعواقب آن سردرگم میشوند.
# POSIX while read -r line; do linecnt=$(($linecnt+1)); done < file echo $linecnt
متأسفانه، این مورد در پوسته Bourne کار نمیکند، sh(1) موروثی پوسته Bourne را برای راه عبور از آن ملاحظه کنید.
استفاده از گروهبندی دستورات و انجام همه چیز در پوسته فرعی:
# POSIX linecnt=0 cat /etc/passwd | { while read -r line ; do linecnt=$((linecnt+1)) done echo "total number of lines: $linecnt" }این در واقع وضعیت پوسته فرعی را تغییر نمیدهد، اما اگر در مابقی کُد شما، چیزی از پوسته فرعی مورد نیاز نیست، آنوقت انهدام محیط موقت محلی پس از تمام شدن کار شما با آن، دقیقاً میتواند موردی باشد که به هر حال شما لازم دارید.
استفاده از جایگزینی پردازش(فقط Bash):
# Bash while read -r line; do ((linecnt++)) done < <(grep PATH /etc/profile) echo "total number of lines: $linecnt"این مورد در اصل مشابه workaround اول در بالا میباشد. ما بازهم فایلی را تغییر مسیر میدهیم، فقط در اینجا فایل یک لوله با نام (fifo) موقت است که توسط جایگزینی پردازش ما برای انتقال خروجی grep ایجاد گردیده است.
استفاده از لوله با نام:
# POSIX mkfifo mypipe grep PATH /etc/profile > mypipe & while read -r line;do linecnt=$(($linecnt+1)) done < mypipe echo "total number of lines: $linecnt"
استفاده از کمک پردازش(ksh و حتی pdksh و bash نسخه ۴ و oksh و mksh..):
# ksh grep PATH /etc/profile |& while read -r -p line; do linecnt=$((linecnt+1)) done echo "total number of lines: $linecnt"
استفاده از HereString(فقط Bash):
read -ra words <<< 'hi ho hum' printf 'total number of words: %d' "${#words[@]}"
عملگر <<< مختص bash (نگارش 2.05b و بعد) میباشد، به هرحال یک روش بسیار پاکیزه و سودمند برای تعیین یک رشته کوچک لفظی برای ورودی فرمان است.
# Bash declare -i linecnt while read -r; do ((linecnt++)) done <<EOF hi ho hum EOF printf 'total number of lines: %d' "$linecnt"
# Bash 4.2 set +m shopt -s lastpipe printf '%s\n' hi{,,,,,} | while read -r "lines[x++]"; do :; done printf 'total number of lines: %d' "${#lines[@]}"Bash نگارش 4.2 رفتار فوقالذکرِ kshمانند در Bash را معرفی نموده. یک هُشدار آن است که کنترل job نباید فعال باشد، که به این وسیله سودمندی آن در یک پوسته محاورهای محدود میگردد.
برای مثالهای بیشتری در ارتباط با چگونگی خواندن ورودی و خُرد کردن آن به کلمات، پرسش و پاسخ شماره 1 را ببینید.
پرسش و پاسخ 24 (آخرین ویرایش 2012-03-05 03:38:07 توسط ormaaj)