در یک حالت کلی، یک توصیفگرفایل (FD) جدید باز خواهید نمود که به fifo اشاره کند، و از طریق آن مینویسید. برای موقعیتهای ساده، شاید پرش از این مرحله امکان پذیر باشد.
اصلیترین روش، استفاده از لولههای با نام است:
mkfifo myfifo cat < myfifo & echo 'a' > myfifo
این کد کار میکند، اما cat بعد از خواندن یک سطر میمیرد. (در حقیقت، آنچه رخ میدهد آن است که وقتی لوله با نام توسط تمام نویسندهها بسته میشود، یک سیگنال وضعیت پایان فایل برای خواننده ارسال میشود. بنا بر این cat، یعنی خواننده، به دلیل آنکه یک پایان فایل در ورودیاش میبیند، خاتمه مییابد.)
اگر بخواهیم چندین مرتبه بدون داشتن شروع مجدد برای خواننده، در یک لوله بنویسیم چه؟ باید تمام دادههای خود را طوری مرتب کنیم که بدون چندین مرتبه باز و بسته کردن لوله فرستاده بشوند.
اگر فرمانها متوالی باشند، میتوانند گروهبندی بشوند:
cat < myfifo & { echo 'a'; echo 'b'; echo 'c'; } > myfifo
یک انتخاب میتواند، استفاده از tail -f به جای cat باشد:
tail -f myfifo & echo 'a' > myfifo # نمیمیرد echo 'b' > myfifo echo 'c' > myfifo
در اینجا مشکل این است که، حتی اگر لوله با نام حذف شود، پردازش tail نمیمیرد. در اسکریپت این مشکلی نیست چون میتوانید tail را موقع خروج kill کنید. همچنین یک گزینه --pid برای tail وجود دارد که در چنین حالاتی میتواند سودمند باشد.
اما اگر آن فرمانها به عللی نتوانند گروهی بشوند یا اگر استفاده از tail یک انتخاب نباشد، راه بهتر تخصیص یک توصیفگر فایل به لوله و نوشتن در آنجا میباشد:
cat < myfifo & # را اختصاص میدهد fd 3 توصیفگر exec 3>myfifo # به جای باز کردن مجدد لوله fd 3 نوشتن در echo 'a' >&3 echo 'b' >&3 echo 'c' >&3 # fd بستن exec 3>&-
بستن FD باعث میشود خواننده لوله نشانه پایان فایل را دریافت کند.
راه حل توصیفگرفایل فوق به خوبی برای نوشتن در یک fifo از یک پوسته منفرد، به خوبی کار میکند، اما در صورتیکه پردازشهای بسیاری نیاز به نوشتن در fifo داشته باشند، اینطور نیست. در این حالت، یک راه چاره باز نگاه داشتن fifo توسط یک نویسنده است:
mkfifo myfifo # باز شده را برای استفاده یک نویسنده حفظ میکند fifo cat > myfifo
یک خواننده را در پوسته دیگری باز کنید:
cat myfifo
آنوقت هر تعداد پوسته که میخواهید برای ارسال دادهها به خواننده به کار ببرید:
echo something > myfifo # خواننده نمیمیرد
برای خاتمه یافتن خواننده، به اولین cat (نویسنده) بازگردید و با استفاده از CTRL-C یک EOF برایش ارسال کنید(یا با استفاده از CTRL-C آن را خاتمه بدهید).
پرسش و پاسخ 85 (آخرین ویرایش 2013-03-07 17:00:39 توسط put92-6-88-165-39-120)
توابع در Bash (همچنین سایر پوستههای هم خانواده شل Bourne) مانند فرمانها کار میکنند: یعنی، آنها فقط یک وضعیت خروج برگشت میدهند که منحصر به اعداد صحیح از 0 تا 255 است. این به منظور استفاده در علامتدهی خطاها میباشد، نه برای بازگرداندن نتایج محاسبات یا سایر دادهها.
اگر نیاز دارید دادههای اختیاری را از یک تابع به فراخواننده آن باز گردانید، حداقل سه شیوه وجود دارد که توسط آنها میتوانید این نیاز را برآورده کنید:
foo() { echo "this is my data" } x=$(foo) echo "foo returned '$x'"
یک اشکال این روش آنست که تابع در یک پوسته فرعی اجرا میشود، و این به معنای آنست که هر تخصیص متغیر و غیره که در تابع انجام شده بر محیط فراخوان کننده تأثیر نمیگذارد (و به واسطه یک fork() متحمل خسارت سرعت هم میگردد). این مطلب بسته به احتیاجات برنامه و تابع شما، میتواند مشکلی باشد یا نباشد. یک اشکال دیگر آنست که هر آنچه توسط تابع foo چاپ میشود، اخذ میگردد و در متغیر قرار داده میشود. اگر تابع foo همچنین چیزهایی را که قرار نیست یک مقدار برگشتی باشند، مینویسد، این مطلب به مشکلاتی منجر میشود. برای جداکردن اعلانهای کاربر و(یا) پیغام خطاها از دادههای برگشتی، آنها را به stderr که توسط فراخواننده اخذ نمیشود تغییر مسیر بدهید.
foo() { echo "running foo()..." >&2 #میفرستد stderr اعلانهای کاربر و خطاها را به echo "this is my data" # این مقدار پایین به متغیر اختصاص داده میشود } x=$(foo) # را چاپ میکند running foo()... echo "foo returned '$x'" # را چاپ میکند foo returned 'this is my data'
foo() { return="this is my data" } foo echo "foo returned '$return'"
اشکال این شیوه آنست که اگر تابع در پوسته فرعی اجرا بشود، آنوقت تخصیص به متغیر سراسری در تابع، توسط فراخواننده دیده نخواهد شد، این بدان معناست که به عنوان مثال، شما قادر به استفاده از تابع در یک خط لوله نخواهید بود.
foo() { echo "this is my data" > "$1" } # !این کد استواری برای مدیریت فایلهای موقتی نیست tmpfile=$(mktemp) # GNU/Linux foo "$tmpfile" echo "foo returned '$(<"$tmpfile")'" rm "$tmpfile" # کرده بودیم trap اگر این برنامه واقعی بود، بررسی خطا و
برای اطلاعات بیشتر در مورد مدیریت فایلهای موقت از درون اسکریپت پوسته، پرسش و پاسخ شماره 62 را ببینید. برای trapها، SignalTrap را ملاحظه کنید.
پرسش و پاسخ 84 (آخرین ویرایش 2012-10-16 17:56:51 توسط GreyCat)
چندین روش بر حسب موارد نیاز، برای بررسی یک متغیر تعریف شده یا غیرتهی یا تابع وجود دارد.
محتویات
همانند بسیاری از زبانها تعیین و تعریف میتوانند مراحل مستقلی باشند. تعیین(declare) یک شیء، دادن نام و نوع داده به آن است، در جایی که تعریف(definition) آن را با یک مقدار مرتبط میکند، که میتواند تهی باشد. در واژگان فنی پوسته یونیکس، set، unset، و null به ترتیب به معنی تعریف نشده، تعریف شده، و تهی میباشند. چون رشته تقریباً تنها نوعداده رایج است، "null" معمولاً به معنی رشته تهی است. در مورد آرایهها، موارد سازگاری بین پوستهها کمتر میشود.
چون تشخیص میان متغیر یا پارامتر تهی و غیرتهی معمولترین هدف است، ما اول آن را بحث خواهیم نمود. اینها رایجترین راهحلهای مرتب شده از بیشترین به کمترین قابلیت حمل میباشند.
test x"$var" != x |
فقط برای پوستههای باستانی Bourne. در اسکریپتهای مدرن از این استفاده نکنید. |
test -n "$var" |
POSIX sh به اضافه پوستههای قدیمیتر Bourne که فاقد [ هستند. |
[ -n "$var" ] |
POSIX sh و اکثر پوستههای Bourne. رویهم رفته بهترین انتخاب برای اسکریپتهای جدید نیازمند سازگاری با POSIX. |
test "$var" |
POSIX sh، استاندارد POSIX تصریح میکند که غیرتهی همیشه صحیح است در حالیکه تهی همیشه غلط است. |
[ "$var" ] |
POSIX sh، مانند مورد فوق بازهم خیلی قابل حمل. |
[[ -n $var ]] |
این مورد و بعدی فقط در Bash/Ksh/Zsh، این با برخی نگارشهای پوسته buggy که گزینه -n صریح را به طور صحیح پردازش نمیکنند سازگار است. |
[[ $var ]] |
به طور کلی در جایی که POSIX مورد نیاز نیست بهترین انتخاب برای Bash و Ksh. به طور معمول دارای بهترین کارایی. در Zsh کار نخواهد کرد. |
(( ${#var} )) |
تقریباً به اندازه مورد قبل قابل حمل. در واقع همیشه آهستهتر. از این مورد برای بررسی رشتههای تهی استفاده نکنید. |
برای بررسی معکوس، آیا یک متغیر تهی است(unset یا به طول صفر)، منطق بالا را برعکس کنید.
test x"$var" = x test -z "$var" [ -z "$var" ] test ! "$var" [ ! "$var" ] [[ -z $var ]] [[ ! $var ]] # یا هر مورد تقلید شده آن کار نخواهد نمود. اگر چنین Zsh همچنین در # در این صفحه را تنظیم کنید-z یا -n قابلیتی مورد نیاز است، راه حل صریح
تمیز دادن میان متغیری که تعریفنشده است و آنکه تعریفشده اما تهی است تا اندازهای ماهرانه، اما امکانپذیر است.
# POSIX ${var+:} false ! ${var+false} [ -n "${var+_}" ] [ "${var+_}" ]
اینکه کدام یک از موارد فوق بهترین اجراست از یک پوسته تا دیگری فرق میکند. اگر POSIX مورد نیاز نیست، راه حل [[ پایین معمولاً بهترین اجرا میباشد.
# Bash/ksh [[ ${var+_} ]]
شیوه دیگری که گهگاهی دیده شده است، بررسی وضعیت typeset -p است. روش فوق باید بر آن ترجیح داده شود.
# Bash/ksh/zsh typeset -p var >/dev/null 2>&1 # موجود باشد صفر را و در غیر آن صورت خطا برمیگرداندvar اگر
Bash 4.2 یک -v test اضافه میکند:
# Bash 4.2 / ksh93 if [[ -v var ]]; then echo "var is defined"; fi
ksh93 به طور افزون از شاخصهای آرایه با -v test پشتیبانی میکند، در حالیکه Bash تا به حال پشتیبانی نمیکند. وقتی که مقصد به طور خاص ksh93 باشد، باید این شیوه ترجیح داده شود.
برای تعیین آنکه آیا تابعی با نام معین قبلاً تعریف شده است، چند جواب وجود دارد، تمام آنها به فرمانهای Bash (یا حداقل پوستههای غیرBourne) نیاز دارند. بررسی آنکه تابعی از قبل تعریف شده است، به ندرت لازم خواهد شد.
typeset -f f >/dev/null # Bash/Ksh - تعریف نشده باشد غلط در غیر آنصورت # .صحیح برمیگرداند [[ $(typeset -f f) ]] # Bash/Ksh - در صورتیکه تعریف نشده باشد چیزی چاپ # نمیکند در غیر آنصورت تعریف تابع را در خروجی میدهد [[ $(type -t f) == function ]] # اگر تعریف شده باشد تابع را بیرون میدهد Bash فقط # است "whence -v" مستعاری برای "type" فرق میکند،(mksh و) ksh در # راه عبوری برای مورد فوق دارند اما دو روش اول بیشتر قابل ترجیح هستند Bash/Ksh isFunction() [[ $(type ${BASH_VERSION:+-t} "$1") == ${KSH_VERSION:+"$1 is a "}function ]]; isFunction f
پرسش و پاسخ 83 (آخرین ویرایش 2012-11-23 09:57:53 توسط ormaaj)
`...` ترکیب دستوری موروثی است که فقط در پوستههای خیلی قدیمی ناسازگار با POSIX لازم میشود. چندین دلیل برای ترجیح دادن همیشگی ترکیب $(...) وجود دارد:
$ echo "`echo \\a`" "$(echo \\a)" a \a $ echo "`echo \\\\a`" "$(echo \\\\a)" \a \\a # Note that this is true for *single quotes* too! $ foo=`echo '\\'`; bar=$(echo '\\'); echo "foo is $foo, bar is $bar" foo is \, bar is \\
نقلقولهای تو در تو داخل $() خیلی بیشتر مناسب هستند.
echo "x is $(sed ... <<<"$y")"
در این مثال، با نقلقولهای اطراف $y به عنوان یک زوج رفتار میشود، به علت آن که آنها داخل $() هستند. این در نگاه اول مغشوش به نظر میآید، زیرا اکثر برنامهنویسان C انتظار دارند با نقلقول قبل از x و نقلقول قبل از $y به عنوان یک زوج رفتار بشود، اما در پوستهها این درست نیست. از طرف دیگر کد زیر،
echo "x is `sed ... <<<\"$y\"`"برای قابل حمل شدن نیاز به \ها در اطراف نقلقولهای داخلی دارد، پوستههای Bourne و Korn به این \ها نیاز دارند، در حالیکه Bash و dash لازم ندارند.
x=$(grep "$(dirname "$path")" file) x=`grep "\`dirname "$path"\`" file`
بعد از دومرحله زشتتر و بدقوارهتر میشود. $() زمینه کاملاً جدید نقلقولی را تحمیل میکند، به طوری که هر چیز درون جایگزینی فرمان محافظت میشود و میتواند مثل اینکه خودش به تنهایی بوده، بدون ارتباط خاص با نقلقول و escaping، با آن رفتار بشود.
تابع $(...) به عنوان یک بسط از لحاظ بصری واضح است. ترکیب نشانه پیشوندی $ سازگار با تمام بسط های دیگر است، که در همان زمان از چپ به راست از درون نقلقولهای دوگانه تفکیک میشوند. فقط نقلقولهای وارونه(backticks) مستثنی میباشند. این مطلب قابلیت خواندن ماشین و انسان را بهبود میدهد، و ترکیب دستوری نامتناقض، زبان را برای خوانندگان محسوستر میکند.
مطابق بالا، اشخاص(امیدواریم)با بسطهای نقلقولی دوگانه و بسطهای جایگزینی با ترکیب دستوری معمول "$..." عادت داده میشوند. تقریباً همیشه نقلقولی کردن جایگزینی فرمان کار صحیحی میباشد، بازهم اکثریت عظیمی از نمونههای `...` پراکنده نقلقولی نشده پیدا میکنیم، شاید به دلیل اینکه آنهایی هنوز از دستور زبان موروثی استفاده میکنند که کمتر ورزیده هستند، یا به سبب ترکیب دستوری متفاوت آن را با سایر بسطها مربوط نمیکنند.
علاوه بر این، کاراکتر ` موقعی که در مجاورت " قرار گیرد به سادگی استتار میشود، که حتی برای خواندن دشوارتر میگردد، مخصوصاً با فونتهای کوچک یا غیر عادی.
پرسش و پاسخ 82 (آخرین ویرایش 2013-01-24 10:53:38 توسط ormaaj)
POSIX فرمان داخلی به نام command تعیین میکند، که میتواند برای این منظور به کار برود:
# POSIX if command -v qwerty >/dev/null; then echo qwerty exists else echo qwerty does not exist fi
در BASH، یک زوج دستور داخلی بیشتر نیز وجود دارد که ممکن است به کار بروند: hash و type. این هم مثالی با استفاده از hash:
# Bash if hash qwerty 2>/dev/null; then echo qwerty exists else echo qwerty does not exist fi
یا، اگر ترجیح میدهید type:
# Bash # .بدون در نظر گرفتن دستورات داخلی و غیره میکند PATH الزام به جستجوی type -P دستور if type -P qwerty >/dev/null; then echo qwerty exists else echo qwerty does not exist fi
پوسته Korn در عوض دارای whence میباشد:
# ksh if whence -p qwerty >/dev/null; then echo qwerty exists else echo qwerty does not exist fi
دستور داخلی command همچنین برای دستورات داخلی پوسته صحیح را باز میگرداند(برخلاف type -P). اگر شما باید به طور مطلق فقط PATH را بررسی کنید، تنها روش POSIX انجام تکرار روی آن است:
# POSIX IsInPath () ( [ $# -eq 1 ] && [ "$1" ] || return 2 set -f; IFS=: for dir in $PATH; do [ -z "$dir" ] && dir=. # رفتار موروثی [ -x "$dir/$1" ] && return done return 1 ) if IsInPath qwerty; then echo qwerty exists else echo qwerty does not exist fi
توجه نمایید که تابع تعریف شده فوق به جای ابروهای معمول، از پرانتزها در اطراف بدنه استفاده میکند. این باعث میشود بدنه در یک پوسته فرعی اجرا گردد، و این به دلیل آنست که ما میخواهیم نیازی به خنثی نمودن set -f یا IFS نداشته باشیم.
رویکرد تکرار کننده در اسکریپتهای configure نیز استفاده میشود. این هم نگارش ساده شدهای از یک چنین تستی:
# Bourne save_IFS=$IFS IFS=: found=no for dir in $PATH; do if test -x "$dir/qwerty"; then echo "qwerty is installed (in $dir)" found=yes break fi done IFS=$save_IFS if test $found = no; then echo "qwerty is not installed" fi
اسکریپتهای واقعی configure معمولاً خیلی بیشتر از این پیچیده هستند، چون آنان ممکن است با سیستمهایی سر و کار داشته باشند که در آنها $PATH به وسیله کاراکترهای کولن جدا نمیشوند، یا سیستمهایی که در آنها شاید برنامههای اجرایی پسوندهای انتخابی مانند .EXE دارند، یا متغیرهای $PATH که دایرکتوری کاری جاری به عنوان یک رشته تهی در آنها گنجانده شدهاند و غیره. اگر به چنین مطالبی علاقمند هستید، پیشنهاد میکنم یک اسکریپت configure واقعی تولید شده با autoconf گنو را بخوانید. آنها بیش از آن بلند و پیچیده میباشند که در این پرسش و پاسخ قرار داده شوند.
فرمان which (که غالباً یک اسکریپت csh است، اگر چه گاهی نیز به صورت باینری کامپایل شده است) برای این منظور قابل اطمینان نیست. which نمیتواند یک کد خروج سودمند تنظیم کند، و حتی نمیتواند خطاها را در stderr بنویسد. بنابر این، برای مطلع شدن از اجرای موفقیت آمیز آن، باید خروجیاش تفکیک بشود(هر جا که خروجی نوشته بشود).
# Bourne. Last resort -- using which(1) tmpval=`LC_ALL=C which qwerty 2>&1` if test $? -ne 0; then # این ماشین یک کد خروج غیرصفر which(1) در حال حاضر فرض خواهیم نمود که اگر # تنظیم کند، به راستی ناموفق بوده. هنوز باید حالتی را ببینیم که در آن # درست مثل موفقیت غلط -- یک عدم موفقیت غلط تنظیم میکند which(1) echo "qwerty is not installed. Please install it." else # صفر را بازگردانده، که به معنی موفقیت آن نیست. جستجو برای رشته های خطا case "$tmpval" in *no\ *\ in\ *|*not\ found*|'') echo "qwerty is not installed. Please install it." ;; *) echo "Congratulations -- it seems you have qwerty (in $tmpval)." ;; esac fi
توجه نمایید که خروجی which(1) موقعی که دستوری را پیدا نمیکند در تمام سکوها یکسان نیست. برای مثال، در HP-UX 10.20، این پیغام خطا را چاپ میکند no qwerty in /path /path /path ...، در OpenBSD 4.1، این پیغام خطا را qwerty: Command not found.، در دبیان (حداقل 3.1 تا 5.0) و SuSE، ابداً چیزی چاپ نمیکند، در Red Hat 5.2، چاپ میکند which: no qwerty in (/path:/path:...)، در Red Hat 6.2، همان پیغام را مینویسد، اما به جای خروجی استاندارد در خطای استاندارد، و در Gentoo، چیزی در stderr مینویسد.
ما قویاً پیشنهاد میکنیم از which استفاده نکنید. به جای آن یکی از دستورات داخلی یا رویکردهای تکرار را به کار ببرید.
پرسش و پاسخ 81 (آخرین ویرایش 2012-03-14 11:57:13 توسط pgas)