`...` ترکیب دستوری موروثی است که فقط در پوستههای خیلی قدیمی ناسازگار با 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)
نمی توانید. مستعارها در bash بینهایت ابتدایی هستند، و در حقیقت برای هیچ مقصود مهمی مناسب نمیباشند. حتی صفحه مستندات bash به طور صریح میگوید:
به جای آن از تابع استفاده کنید. برای مثال:
settitle() { case $TERM in *xterm*|*rxvt*) echo -en "\e]2;$1\a";; esac; }
اوه، در ضمن: مستعارها در اسکریپتها مجاز نیستند. آنها فقط در پوستههای محاورهای مجاز میباشند، و حقیقتاً چنین است چونکه اگر آنها به طور کلی برچیده میشدند، کاربران با صدای بلند فریاد میکشیدند. اگر شما یک اسکریپت مینویسید، همیشه به جای آن از تابع استفاده کنید.
پرسش و پاسخ 80 (آخرین ویرایش 2008-11-22 23:25:45 توسط GreyCat)
این در واقع سه پرسش مختلف است، بنابراین ما پاسخ را به سه قسمت تفکیک کردهایم.
آسانترین روش برای مطابقت با سطرهایی که شامل هر دو رشته foo و bar هستند استفاده از دو فرمان grep میباشد:
grep foo | grep bar grep foo "$myfile" | grep bar # برای آنهایی که نیاز به نگهداری دستی دارند
این کار همچنین میتواند با یک egrep انجام بشود، هر چند(به طوریکه احتمالاً میتوانید حدس بزنید) این فرمان نمیتواند به طور واقعی برای سنجش بیش از دو الگو به کار برود:
egrep 'foo.*bar|bar.*foo'
اگر ترجیح میدهید، میتوانید با یک جمله sed یا awk به این مقصود نائل شوید:
sed -n '/foo/{/bar/p}' awk '/foo/ && /bar/'
اگر نیاز به راه حل سنجش awk برای تعداد اختیاری الگوها دارید، میتوانید یک سطر فرمان awk ممتد طرحریزی نمایید:
# bashو ksh93 درپوستههای # را ایجاد میکند awk "/$1/&&/$2/&&...." # دادههایی که میخواهند مطابقت شوند باید در ورودی استاندارد باشند # سطرهای انطباق یافته را در خروجی استاندارد مینویسد multimatch() { (($# < 2)) && { echo "usage: multimatch pat1 pat2 [...]" >&2; return 1; } awk "/$1/$(printf "&&/%s/" "${@:2}")" }
یا، نگارش POSIX:
# POSIX multimatch() { [ $# -lt 2 ] && { echo "usage: multimatch pat1 pat2 [...]" >&2; return 1; } __p1=$1 shift awk "/$__p1/$(printf "&&/%s/" "$@")" }
افسوس، توابع POSIX متغیرهای محلی ندارند. همچنین، در صورتیکه هر یک از الگوها شامل کاراکترهای
یک نگارش POSIX که عبارتهای منظم را در اسکریپت awk تعبیه نمیکند.
# POSIX multimatch() { awk 'BEGIN{for(i=1;i<ARGC;i++) a[i]=ARGV[i]; ARGC=1} {for (i in a) if ($0 !~ a[i]) next; print}' "$@" }
روشهای بسیار زیادی برای مطابقت سطرهای شامل foo یا bar وجود دارد. با گزینه -e میتوان الگوهای چندتایی را با grep به کار برد:
grep -e 'foo' -e 'bar'
یا میتوانید یک الگو با egrep (یا grep -E) بسازید:
egrep 'foo|bar' grep -E 'foo|bar'
(نمیتوانید ازعملگر اتصال | با grep ساده استفاده کنید. این عملگر | فقط در عبارتهای منظم توسعهیافته معتبر میباشد.)
این کار همچنین میتواند با sed، awk، و غیره انجام بشود.
awk '/foo|bar/'
رویکرد awk برتری آن را دارد که به شما اجازه استفاده از سایر ویژگیهای awk روی سطرهای انطباق یافته را میدهد.
مطابقت سطرهایی که شامل هیچ یک از رشتههای foo و bar نیستند:
grep -E -v 'foo|bar' # را ترجیح میدهند egrep -v 'foo|bar' بعضی اشخاص
اگر میخواهید مطابقت کنید که فایلها (به جای سطرها) شامل هر دو رشته foo و bar هستند، چندین رویکرد عملی وجود دارد. سادهترین(اگر چه ضرورتاً کارآمدترین نیست) دوبار خواندن فایل است:
grep -q foo "$myfile" && grep -q bar "$myfile" && echo "Found both"
راه حل استفاده دوگانه grep -q این مزیت را دارد که در هر نوبت خواندن، در جایی که مورد انطباقی پیدا شود توقف میکند، بنابراین اگر یک فایل حجیم داشته باشید، اما کلمات در ابتدای آن باشند، فقط قسمت اول فایل خوانده میشود. متأسفانه، اگر انطباقها در انتهای فایل باشند(وضعیت وخیم: در انتهاییترین سطرهای فایل) شما کل فایل را دوبار میخوانید.
رویکرد دیگری یکبار خواندن فایل، و حفظ نمودن اثر آنچه دیدهاید، در ضمن پیشروی در فایل میباشد. در awk:
awk '/foo/{a=1} /bar/{b=1} a&&b{print "both found";exit} END{if (a&&b){ exit 0} else{exit 1}}'
این کد فایل را یک بار میخواند، موقعی که هر دو الگو منطبق گردیدند توقف میکند. مهم نیست چه اتفاقی رخ میدهد، پس از آن بلوک END اجرا میشود، و مطابق آن وضعیت خروج تنظیم میگردد.
اگر میخواهید بررسیهای اضافی در محتویات فایل انجام بدهید، این راهکار awk میتواند کاملاً به آسانی وفق داده شود.
پرسش و پاسخ 79 (آخرین ویرایش 2011-04-15 12:16:35 توسط GreyCat)
خوب، اول از همه، من میدانم ممکن است افردای نیز هم اکنون درحال خواندن این مطلب باشند که حتی پرسش را متوجه نمیشوند. اینجا، این کد کار نمیکند:
{ echo oldpass; echo newpass; echo newpass; } | passwd # !این کار نمیکند
اصلاً کاری نمیتوانید بکنید که بتواند در bash عملی بشود. passwd(1) از ورودی استاندارد نمیخواند. این مطلب تعمدی است. برای حراست از شماست. هرگز کلمات عبور برای قرار گرفتن در برنامهها یا تولید شدن توسط برنامهها در نظر گرفته نشدهاند. آنها برای آن در نظر گرفته شدهاند که با انگشتان یک انسان حقیقی، با یک ذکاوت عملیاتی وارد بشوند، و هرگز اصلاً در جایی نوشته نشوند. بنابراین قبل از اینکه ادامه بدهید، احتمالی را که مؤلفین passwd(1) در فکر آن بودند، درنظر بگیرید، و شاید شما نباید سعی در اسکریپت نمودن ورودی passwd(1) بنمایید.
با این وجود، انبوهی پرسش از کاربران به دست ما میرسد که آنها چگونه میتوانند 35 سال امنیت یونیکس را خنثی کنند. و همکاری اشخاصی با راه حلهای مطلوبِ حذف امنیت را با این صفحه مشاهده میکنیم. اگر شما بازهم فکر میکنید آنچه میخواهید این است، ادامه دهید.
اولین رویکرد، ساختن کلمه عبور بهم ریخته خودتان(DES، MD5، Blowfish، یا هر چیزی که سیستم عامل شما استفاده میکند) با استفاده از ابزارهای غیر استانداردی از قبیل http://wooledge.org/~greg/crypt/ یا بسته mkpasswd دبیان/اوبونتو، را شامل میشود. سپس باید آن کلمه عبور درهم ریخته را همراه با فیلدهای اضافی در یک سطر از فایل password-hash سیستم محلی خودتان (شاید /etc/passwd، یا /etc/shadow، یا /etc/master.passwd، یا /etc/security/passwd، یا ... باشد) بنویسید. این کار نیاز به آن دارد که شما صفحات man در سیستم خود را بخوانید، تا دریابید پسورد بهمریخته کجا قرار میگیرد، فایل چه قالببندی نیاز دارد، و سپس کُدی ایجاد کنید که آنرا در آن قالب به تفصیل بنویسد.
یک گونه فرعی آن، مستلزم استفاده از یک ابزار سیستمی خاص برای نوشتن سطرِ کلمه عبور درهم ریخته طراحی شده شما، میباشد. برای مثال، در دبیان/اوبونتو، useradd -m joe -s /bin/bash -p "$(mkpasswd "$password")" به طوری که ما فهمیدهایم، میتواند کارکند.
دومین رویکرد استفاده از expect یا معادل python آن است. من گمان میکنم expect حتی دقیقاً این موضوع را به عنوان یکی از مثالهای رسمیاش دارد.
سرانجام، ابزارهای سیستمی طراحی شده برای این مقصود ممکن است از قبل در سیستم شما موجود باشد. برای مثال، بعضی از سیستمهای گنو-لینوکس دارای فرمان newusers(8) هستند که به طور خاص برای این منظور طراحی شدهاند، یا ابزار chpasswd(8) که میتواند وادار به انجام انواعی از این کارها بشود. یا ممکن است یک پرچم --stdin در فرمان passwd خود داشته باشند. همچنین فرمانهایی از قبیل apropos users یا man -k account را برای دیدن مورد دیگری که شاید وجود داشته باشد امتحان کنید. خلاق باشید.
همچنین پرسش و پاسخ 69 -- میخواهم یک ارتباط ssh (یا scp، یا sftp) را خودکار نمایم، اما نمیدانم چطور کلمه عبور را ارسال کنم را ببینید.
در کنار این مطلب، عکس این پرسش نیز مشکلی است. حداقل تحت لینوکس ، پنهان کردن برنامه به نحوی که ترمینال کنترل کننده وادار شود به طور انتزاعی به هر نوع ورودی و خروجی دلخواه شما متصل بشود، امری بدیهی است. این به معنای آنست که از نطر امنیتی بسیار دشوار است تضمین شود که واقعاً یک کاربر با دسترسی محلی است که به طور مستقیم از صفحه کلید، ورودی را به برنامه شما میدهد. غالباً اشخاص این کار را با خواندن از /dev/tty انجام میدهند. این درست مانند روشی است که برنامه passwd عمل میکند، فقط قدم کوچکی در سست کردن عادات امنیتی نا مناسبی نظیر ذخیره کلمات عبور در فایلهای متن ساده است. کد زیر Bash را اجرا میکند، که برنامهای در FD 3 را میخواند که به طور غیر هوشمندانه ورودیاش را از طریق یک لوله (که به آسانی میتوانست دقیقاً یک فایل باشد)، با استفاده از یک تابع کتابخانه استاندارد پایتون میخواند.
~ $ { echo 'o hi there' | python -c 'import pty; pty.spawn(["bash", "/dev/fd/3"])'; } <<"EOF" 3<&0- <&2 # ممانعت میکند، اثر واقعی ندارد echo از قطع ارتباط ورودی استاندارد <&2 { stty -echo read -p 'Password: ' passw printf '\n%s\n' "password is: $passw" stty echo } </dev/tty EOF o hi there Password: password is: o hi there # نگارش بدون استفاده از پایتون #{ echo 'o hi there' | script -c "bash /dev/fd/3" /dev/null; } <<"EOF" 3<&- 3<&0- <&2 # لینوکس { echo 'o hi there' | script -q /dev/null bash /dev/fd/3; } <<"EOF" 3<&- 3<&0- <&2 # FreeBSD و Mac OS X در { stty -echo read -p 'Password: ' passw printf '\n%s\n' "password is: $passw" stty echo } </dev/tty EOF
علاوه بر این، خواندن از /dev/tty کمی رنجش آور است زیرا طریقهای که کاربران انتظار دارند تغییر مسیرهایشان عمل کنند را نقض میکند. پس این کار را انجام ندهید. بهتر است از [[ -t 0 ]] جهت بازرسی tty و رفتار کردن بر طبق موقعیت استفاده کنید. حتی موقعی که یک مدیر سیستم رفتار معینی را انتظار دارد که بر اساس آن I/O را تغییر میدهد، این مطلب میتواند رنجش آور باشد. اگر شما باید یکی از این ترفندها را به کار ببرید، آن را مستند کنید، و گزینهای برای غیر فعال نمودن رفتار مشروط I/O فراهم نمایید.
پرسش و پاسخ 78 (آخرین ویرایش 2013-03-28 10:54:50 توسط p54A33276)