فقط سطرهای زیر را در فایل /etc/inputrc یا فایل ~/.inputrc خود اضافه کنید:
"\e[A":history-search-backward "\e[B":history-search-forward
سپس bash را مجدداً راهاندازی کنید( یا logout نموده ودوباره وارد شوید، یا با اجرای exec bash).
readline(قسمتی از bash که ورودی ترمینال را اداره میکند) نام کلیدها ازقبیل up arrow را نمیشناسد. به جای آن شما باید به طور دستی آن رشته escape که کلید مورد نظر به ترمینال بخصوص شما ارسال میکند را تشحیص بدهید(به طور معمول با فشردن Ctrl-V و سپس کلید مورد پرسش)، و آنرا درفایل .inputrc درج کنید، به طوری که در فوق نشان داده شد. \e رشته کاراکتر Escape در readline را معنی میدهد. ترفند Ctrl-V کاراکتر Escape را به صورت ^[ نشان میدهد. شما باید تشخیص بدهید که آن ^[ مقدم، یک کاراکتر Escape است، و خودتان جایگزینی را انجام بدهید.
پرسش و پاسخ 51 (آخرین ویرایش 2008-11-22 21:53:31 توسط GreyCat)
برخی اشخاص کوشش میکنند مواردی مشابه این کد را انجام بدهند:
# مثالی که کار نمیکند
args="-s 'The subject' $address"
mail $args < $body
این کُد به دلیل تفکیک کلمه و به علت آنکه نقلقولهای منفرد داخل متغیر لفظی هستند نه گرامری، شکست میخورد. وقتی که $args بسط داده میشود، چهار کلمه میشود. 'The دومین کلمه، و subject' سومین کلمه است.
برای بدست آوردن یک درک بهتر از آنکه شل چگونه تعیین میکند کدام شناسهها در دستور شما هستند، بخش شناسه ها را بخوانید.
بنابراین، چطور این کار را انجام بدهیم؟ تمام آن بستگی به این دارد که چه کاری است!
حداقل سه وضعیت وجود دارد که اشخاص میخواهند فرمانها، یا شناسههای فرمان را به زور داخل یک متغیر قرار بدهند و سپس آنها را اجرا کنند. هر یک از حالتها نیاز به مدیریت جداگانهای دارد.
اگر شما میخواهید فرمانی را برای استفاده بعدی آن در ظرفی قرار بدهید، تابع را به کار ببرید. متغیرها داده را نگاه میدارند، توابع کُدها را نگاه میدارند.
pingMe() {
ping -q -c1 "$HOSTNAME"
}
[...]
if pingMe; then ..
ریشه موضوع شرح داده شده در فوق، آنست که شما به روشی برای نگهداری هر شناسه به عنوان یک کلمه جداگانه، حتی اگر شناسهها محتوی فاصلهها باشند، نیاز دارید. نقلقولها این کار را انجام نخواهند داد، اما یک آرایه انجام میدهد.
فرض کنید اسکریپت شما میخواهد یک ایمیل ارسال کند. شاید شما مواردی داشته باشید که بخواهید موضوع درج کنید و موارد دیگری که نخواهید. بخشی از اسکریپت شما که آن ایمیل را ارسال میکند میتواند متغیری به نام subject را کنترل کند، برای تعیین آنکه آیا شما به فراهم نمودن شناسههای اضافی برای فرمان mail نیاز دارید. یک برنامهنویس بیتجربه ممکن است چیزی مانند این را مطرح کند:
# این را انجام ندهید
args=$recipient
if [[ $subject ]]; then
args+=" -s $subject"
fi
mail $args < $bodyfilename
به طوری که دیدهایم، این رویکرد وقتیکه subject شامل فضای سفید باشد ناموفق است. واقعاً به اندازه کافی قوی نیست.
همینطور، اگر شما به راستی نیاز دارید یک فرمان به طور پویا(متغیر نسبت به زمان) ایجاد کنید، هر شناسه را در یک عضو جداگانه آرایه قرار بدهید، این چنین:
# یا بالاتر bash 3.1 مثال کارگر در
args=("$recipient")
if [[ $subject ]]; then
args+=(-s "$subject")
fi
mail "${args[@]}" < "$bodyfilename"
(برای جزئیات بیشتر در مورد ترکیب دستوری آرایه پرسش و پاسخ شماره 5 را ببینید.)
اغلب، این پرسش موقعی میرسد که شخصی در حال تلاش برای استفاده از dialog برای ساختن یک منوی متحرک میباشد. فرمان dialog نمیتواند hard-coded زیرنویس 1 بشود، به دلیل آنکه پارامترهایش بر اساس دادههایی که تنها در زمان اجرا در دسترس هستند، فراهم میشود(به عنوان مثال تعداد اقلام منو). برای یک مثال از چگونگی انجام این کار به طور صحیح، پرسش و پاسخ شماره 40 را ببینید.
شما به طور کلی نخواهید که نام فرمانها یا گزینههای دستور را در متغیرها قرار بدهید. متغیرها باید محتوی دادههایی باشند که شما میخواهید به فرمان عبور بدهید، مانند نامهای کاربری، نام میزبانها، درگاهها، متن، و غیره. آنها نباید محتوی گزینههای تعیین شده برای یک فرمان یا ابزار معین باشند. چنین مواردی متعلق به توابع هستند.
در مثال mail، ما در ترکیب دستوری فرمان mail یونیکس، وابستگی hard-coded فراهم نمودهایم -- و مخصوصاً نگارشهایی از فرمان mail که اجازه میدهند موضوع بعد از گیرنده تعیین بشود، که شاید همیشه این حالت نباشد. شخص نگهدارنده اسکریپت ممکن است تصمیم بگیرد ترکیب دستوری را به طوری اصلاح نماید که گیرنده آخر ظاهر شود، که صحیحترین شکل است، یا ممکن است آنها به سبب تغییرات سیستم پستی داخلی شرکت، به طور کلی mail را تعویض کنند، و مواردی از این قبیل. داشتن چندین فراخوانی mail پراکنده در سرتاسر اسکریپت در این موقعیت وضع را پیچیده میکند.
کاری که احتمالاً باید انجام دهید، این است:
# POSIX
# ارسال ایمیل به کسی
# بدنه نامه را از ورودی استاندارد میخواند
#
# sendto address [subject]
#
sendto() {
# unset -v IFS
# mail ${2:+-s "$2"} "$1"
MailTool ${2:+--subject="$2"} --recipient="$1"
}
sendto "$address" "The Subject" <"$bodyfile"
اینجا، بسط پارامتر کنترل میکند $2 (موضوع اختیاری) به چیزی بسط یافته است. اگر اینطور باشد، بسط -s "$2" را به فرمان mail اضافه میکند. اگر نه، به هیچ وجه بسط، گزینه -s را اضافه نمیکند.
پیادهسازی اصلی اسکریپت، فرمان استاندارد یونیکس، mail(1) را به کار میبرد. بعداً اسکریپت توضیحگذاری شد و با چیزی به نام MailTool که به طور فیالبداهه مخصوص این مثال ساخته شده بود، تعویض گردید. اما به روشن ساختن این مفهوم خدمت میکرد که: حتی اگر ابزار پشتیبان تغییر نماید، فراخوانی تابع تغییر نمیکند. همچنین توجه نمایید که، مثال mail(1) فوق، برای جداکردن گزینه شناسه از بسط پارامتر نقلقولی شده داخلی به تفکیک کلمه استناد میکند. این یک استثنای قابل توجه است که در آن تفکیک کلمه قابل قبول و مطلوب است. این مورد بی خطر است زیرا گزینه کُد شده به طور ایستا، شامل هیچ کاراکتر glob نمیباشد، و بسط پارامتر برای ممانعت از globbing بعدی نقلقولی میشود. شما باید مطمئن شوید که IFS به منظور به دست آوردن نتایج مورد انتظار، به یک مقدار معقول تنظیم گردیده است.
دلیل دیگری که مردم سعی میکنند فرمانها را در متغیرها قرار بدهند این است که آنها میخواهند اسکریپتهایشان هر فرمان را قبل از اجرای آن چاپ کند. اگر تمام آنچه شما میخواهید این است، پس به سادگی از دستور set -x استفاده کنید، یا اسکریپت خود را با #!/bin/bash -x یا bash -x ./myscript احضار کنید. توجه کنید که میتوانید این حالت را با استفاده از set +x و set -x خاموش و روشن کنید.
شایان توجه است که نمیتوانید یک خط لوله دستور را داخل یک متغیر آرایهای قرار بدهید و بعد با استفاده از تکنیک "${array[@]}" آن را اجرا کنید. تنها راه ذخیره یک خط لوله در یک متغیر، اضافه نمودن(با احتیاط!) لایهای از نقلقولها در صورت لزوم ، ذخیره در یک متغیر رشتهای، و سپس استفاده از eval یا sh برای اجرای متغیر میباشد. این به دلایل امنیتی پیشنهاد نمیشود. همان مورد با فرمانهای در بر گیرنده تغییر مسیر، جملات if یا while، وغیره فراهم میگردد.
بعضی اشخاص دچار دردسر میشوند به علت آنکه میخواهند اسکریپتی داشته باشند که دستوراتشان از جمله تغییر مسیرها را قبل از اجرای آنها چاپ کند. set -x دستور را بدون تغییرمسیرها نمایش میدهد. اشخاص سعی میکنند با انجام موردی مانند کُد زیر آن را رفع و رجوع کنند:
# مثالی که عمل نمیکند
command="mysql -u me -p somedbname < file"
((DEBUG)) && echo "$command"
"$command"
(این به قدری رایج است که من به طور صریح آن را قرار میدهم، ولواینکه گمان میکنم تکرار مطلبی است که قبلاً نوشتم.)
یکبار دیگر، این کار نمیکند. حتی یک آرایه در اینجا کار نمیکند. تنها موردی که در اینجا عمل میکند پوشش دادن فرمان با دقت زیاد برای مطمئن شدن از آنکه فوق کاراکترها باعث مشکلات خطیر امنیتی نخواهند شد، و سپس استفاده از eval یا sh برای خواندن مجدد دستور است. لطفاً این کار را انجام ندهید! یک روش برای ثبت وقایع کامل فرمان بدون متوسل شدن به استفاده از eval یا sh، استفاده از DEBUG trap میباشد. یک نمونه کد عملی:
trap 'printf %s\\n "$BASH_COMMAND" >&2' DEBUG
با فرض اینکه شما در حال ثبت کردن خطای استاندارد میباشید.
توجه کنید که نمایندگی تغییر مسیر، به وسیله BASH_COMMAND باز هم تحت تأثیر این باگ قرار میگیرد. به نظر میرسد تا اندازهای در git تعمیر شده، اما کامل نیست. انتظار نداشته باشید صحیح باشد.
اگر شما به اندازهای سرتان با آن جایتان بازی میکند که باز هم فکر میکنید نیاز دارید هر دستوری که در صدد اجرای آن میباشید قبل از اجرایش در خروجی نوشته شود، و تمام تغییر مسیرها را نیز شامل شود، پس تنها این کار را انجام بدهید:
# مثال کارگر
echo "mysql -u me -p somedbname < file"
mysql -u me -p somedbname < file
به هیچ وجه متغیر استفاده نکنید. دقیقاً فرمان را کپی و paste کنید، یک لایه نقلقول اضافی در اطراف آن قرار بدهید(گاهی اوقات مهارتآمیز)، و یک echo پیش از آن ضمیمه کنید.
پیشنهاد شخصی من دقیقاً استفاده از set -x و دلواپس آن نبودن است.
پرسش و پاسخ 50 (آخرین ویرایش 2013-04-13 18:52:30 توسط geirha)
tail -f رشد یک فایل ثبت وقایع را به شما نشان میدهد. در بعضی سیستمها(برای مثال OpenBSD)، این به طور خودکار تبدیل یک فایل لاگ به فایل جدیدی با همان نام را دنبال میکند(که به طور معمول همانست که شما میخواهید). برای به دست آوردن همین توانایی در سیستمهای گنو، به جای آن از tail -F استفاده کنید.
این فقط در صورتی مفید است که به روزرسانیهای فایل پس از آخرین نوبتی که آنرا دیدهاید، مورد نیازتان باشد.
# آغاز میشود n=1 با تنظیم tail -n $n testfile; n="+$(( $(wc -l < testfile) + 1 ))"
هر بار فراخوانی این کُد، به روزرسانی فایل را از جایی که آخرین بار متوقف شدهایم ارائه میدهد. اگر شماره سطر جایی که میخواهید از آنجا شروع کنید را میدانید، n را برابر آن عدد قرار بدهید.
پرسش و پاسخ 49 (آخرین ویرایش 2010-06-25 20:17:30 توسط MatthiasPopp)
فرمان eval برای سوءاستفاده به شدت قدرتمند و بینهایت آسان است.
باعث میشود کُد شما به جای یکبار دوبار تجزیه بشود، این به معنی آنست که برای مثال، اگر کُد شما دارای متغیر مرجع باشد، تفکیک کننده پوسته، محتوای آن متغیر را ارزیابی خواهد نمود. اگر متغیر محتوی فرمان پوسته باشد، پوسته میتواند آن فرمان را اجرا کند، آیا شما میخواهید اینطور باشد یا خیر. این مطلب میتواند به نتایج غیر منتظره منجر گردد، مخصوصاً موقعی که متغیرها بتوانند از منابع غیرقابل اعتماد خوانده شوند(مانند کاربران یا فایلهای تولید شده کاربران).
"eval" غلط املایی رایج evil(مترجم: زیانبار، ناشناخته) است. بخشی از این پرسش و پاسخ که به فاصلهها در نام فایلها میپردازد، قبلاً شامل اسکریپت زیر تحت عنوان «ابزار مفید(که احتمالاً به اندازه تکنیک
Syntax : nasty_find_all <path> <command> [maxdepth]
# !این کد زیانبار است و هرگز نباید به کار برود
export IFS=" "
[ -z "$3" ] && set -- "$1" "$2" 1
FILES=`find "$1" -maxdepth "$3" -type f -printf "\"%p\" "`
# هشدار، شرارت
eval FILES=($FILES)
for ((I=0; I < ${#FILES[@]}; I++))
do
eval "$2 \"${FILES[I]}\""
done
unset IFS
این اسکریپت جستجوی بازگشتی فایلها و اجرای فرمان معین شده کاربر روی آنها، حتی اگر نام آنها شامل سطر جدید یا فاصله یا هردو باشد را فرض کرده بود. مؤلف گمان کرده بود که find -print0 | xargs -0 برای مقاصدی از قبیل فرمانهای چندگانه مناسب نمیباشد. این اسکریپت با یک توضیح تعلیمی درهریک از تمام سطرهای در بر گیرنده همراه بود، که ما از آنها صرفنظر میکنیم.
در دفاع از اسکریپت، به این شکل کار میکند:
$ ls -lR .: total 8 drwxr-xr-x 2 vidar users 4096 Nov 12 21:51 dir with spaces -rwxr-xr-x 1 vidar users 248 Nov 12 21:50 nasty_find_all ./dir with spaces: total 0 -rw-r--r-- 1 vidar users 0 Nov 12 21:51 file?with newlines $ ./nasty_find_all . echo 3 ./nasty_find_all ./dir with spaces/file with newlines $
اما این را ملاحظه نمایید:
$ touch "\"); ls -l $'\x2F'; #"
شما درست فایلی به نام "); ls -l $'\x2F'; # ایجاد نمودهاید.
حالا
FILES=(""); ls -l $'\x2F'; #"
که خودش میشود دوجمله FILES=(""); و ls -l / تبریک، شما دقیقاً اجرای دستورات دلخواه را مجاز نمودهاید.
$ touch "\"); ls -l $'\x2F'; #" $ ./nasty_find_all . echo 3 total 1052 -rw-r--r-- 1 root root 1018530 Apr 6 2005 System.map drwxr-xr-x 2 root root 4096 Oct 26 22:05 bin drwxr-xr-x 3 root root 4096 Oct 26 22:05 boot drwxr-xr-x 17 root root 29500 Nov 12 20:52 dev drwxr-xr-x 68 root root 4096 Nov 12 20:54 etc drwxr-xr-x 9 root root 4096 Oct 5 11:37 home drwxr-xr-x 10 root root 4096 Oct 26 22:05 lib drwxr-xr-x 2 root root 4096 Nov 4 00:14 lost+found drwxr-xr-x 6 root root 4096 Nov 4 18:22 mnt drwxr-xr-x 11 root root 4096 Oct 26 22:05 opt dr-xr-xr-x 82 root root 0 Nov 4 00:41 proc drwx------ 26 root root 4096 Oct 26 22:05 root drwxr-xr-x 2 root root 4096 Nov 4 00:34 sbin drwxr-xr-x 9 root root 0 Nov 4 00:41 sys drwxrwxrwt 8 root root 4096 Nov 12 21:55 tmp drwxr-xr-x 15 root root 4096 Oct 26 22:05 usr drwxr-xr-x 13 root root 4096 Oct 26 22:05 var ./nasty_find_all ./dir with spaces/file with newlines ./ $
تعویض ls -l با rm -rf یا وخیمتر، قدرت خلاقه زیادی نمیخواهد.
کسی ممکن است با خود بگوید اینها رویدادهای مشکوک است، اما کسی این نیرنگ را به کار نمیبرد. تمام آن فقط یک کاربر بداندیش میخواهد، یا شاید محتملتر، کاربر مبتدی که موقع رفتن به حمام ترمینال خود را قفل نشده ترک میکند، یا اسکریپت PHP نوشته شده برای ارسال فایل که سلامت نام فایل را کنترل نمیکند، یا فردی مرتکب اشتباهی مانند شخصی بشود که خودش اجازه اجرای کُد اختیاری را جایز نموده است(اکنون به جای محدود بودن به کاربران وب ، یک ضارب میتواند از nasty_find_all برای عبور به محبس chroot و به دست آوردن امتیازات اضافی استفاده کند)، یا استفاده از یک سرویس گیرنده IRC یا IM که در پذیرش نام فایلها برای انتقال فایل یا لاگهای مکالمه بیش از حد آزادیخواه است، و غیره.
رایجترین استفاده صحیح از فرمان eval خواندن متغیرها از خروجی برنامهایست که مخصوصاً طراحی شده برای استفاده به این طریق است. برای مثال،
# در سیتمهای قدیمی، بعد از تغییر اندازه پنجره، شخص این را باید اجرا کند eval `resize` # SSH کمتر ابتدایی: گرفتن عبارت عبور برای یک کلید محرمانه # اجرا میشود .xsession یا .profile این به طور نوعی از یک نوع # به تمام پردازشهای نشست کاربر ssh متغیرهای تولید شده توسط کارگزار # .احتمالی از آن ارث میبرد ssh صادر میشوند، به طوری که یک eval `ssh-agent -s`
eval کاربردهای دیگری دارد، مخصوصاً موقع ایجاد متغیرهای فوقالعاده(متغیرهای غیرمستقیم مرجع). این هم یک مثال از یک روش تجزیه گزینههای خط فرمان که پارامترها را نمیپذیرد:
# POSIX
#
# تولید متغیرهای گزینه به طور پویا، احظار ش را امتحان کنید
#
# sh -x example.sh --verbose --test --debug
for i in "$@"
do
case "$i" in
--test|--verbose|--debug)
shift # حذف گزینه خط فرمان
name=${i#--} # حذف پیشوند گزینه
eval "$name='$name'" # از نو ساختن متغیر
;;
esac
done
echo "verbose: $verbose"
echo "test: $test"
echo "debug: $debug"
بنابراین، چرا این نگارش قابل پذیرش است؟ قابل پذیرش است به دلیل آنکه ما فرمان eval را به طوری محدود کردهایم که فقط موقعی اجرا خواهد شد که ورودی یکی از مقادیر معلوم مجموعه محدود باشد. بنابراین، هرگز نمیتواند توسط کاربر برای اجرای دستور اختیاری مورد سوءاستفاده قرار گیرد -- هر ورودی توأم با دغلبازی، با یکی از سه ورودی مجاز از پیش تعریف شده مطابقت نخواهد کرد. این مغایرت قابل قبول نخواهد بود:
# !کُد خطرناک، این را به کار نبرید
for i in "$@"
do
case "$i" in
--test*|--verbose*|--debug*)
shift # حذف گزینه خط فرمان
name=${i#--} # حذف پیشوند گزینه
eval "$name='$name'" # درست کردن گزینه از نو
;;
esac
done
تمام آنچه تغییر نموده آنست که ما سعی نمودهایم مثال خوب قبلی(که کار خیلی زیادی انجام نمیدهد) را به این طریق ، با اجازه دادن به دریافت مواردی مانند --test=foo سودمند کنیم. اما نگاه کنید که چه چیزی را فعال میسازد:
$ ./foo --test='; ls -l /etc/passwd;x=' -rw-r--r-- 1 root root 943 2007-03-28 12:03 /etc/passwd
یکبار دیگر: با مجاز نمودن آنکه فرمان eval با ورودی فیلتر نشده کاربر استفاده بشود، ما اجرای فرمان دلخواه را مجاز کردهایم.
اگر چه، تخصیص یک متغیر اختیاری توسط eval با استفاده از این ترکیب دستوری کاملاً بی خطر میباشد:
eval "$varname=\$whatever"
البته، این ترکیب فرض میکند که $varname نام یک متغیر معتبر میباشد.
آیا این نمیتوانست با declare بهتر انجام بشود؟ به عنوان مثال:
for i in "$@"
do
case "$i" in
--test|--verbose|--debug)
shift # حذف گزینه خط فرمان
name=${i#--} # حذف پیشوند گزینه
declare $name=Yes # تنظیم مقدار پیش فرض
;;
--test=*|--verbose=*|--debug=*)
shift
name=${i#--}
value=${name#*=} # کمیت جایی بعد از اولین کلمه و = است
name=${name%%=*} # محدود نمودن نام به تنها یک کلمه
# (حتی اگر یک = دیگر در مقدار باشد)
declare $name="$value" # درست کردن گزینه از نو
;;
esac
done
توجه کنید که --name برای حالت پیش فرض ، و --name=value قالبهای لازم هستند.
به نظر میرسد declare نوعی تفکیک کننده جادویی دارد، بیشتر مانند [[ . در اینجا آزمایشی هست که من در bash نگارش 3.1.17 انجام دادهام:
griffon:~$ declare foo=x;date;x=Yes Sun Nov 4 09:36:08 EST 2007 griffon:~$ name='foo=x;date;x' griffon:~$ declare $name=Yes griffon:~$ echo $foo x;date;x=Yes
آشکار است که، حداقل در bash, فرمان declare خیلی مطمئنتر از eval است.
attoparsec:~$ echo $BASH_VERSION
4.2.24(1)-release
attoparsec:~$ danger='( $(printf "%s!\n" DANGER >&2) )'
attoparsec:~$ declare safe=${danger}
attoparsec:~$ declare -a unsafe
attoparsec:~$ declare unsafe=${danger}
DANGER!
attoparsec:~$
متغیرهای عادی ممکن است با declare بی خطر باشند، اما متغیرهای آرایهای بیخطر نیستند.برای لیستی از روشهای ارجاع یا مقداردهی غیر مستقیم متغیرها بدون استفاده از eval، لطفاً پرسش و پاسخ شماره 6 را ببینید. (این بخش قبل از آن پاسخ نوشته شده بود، اما من آن را به عنوان یک منبع در اینجا قرار دادهام.)
یک رویکرد دیگر آن است که کُد خطرناک میتواند در یک تابع پوشانیده بشود. برای مثال به جای انجام کاری مانند این:
eval "${ArrayName}"'="${Value}"'
حال آنکه مثال فوق به طور قابل قبولی صحیح است، اما هنوز قابلیت آسیبپذیری دارد. توجه کنید چه اتفاقی میافتد اگر به صورت زیر انجام بدهیم.
ArrayName="echo rm -rf /tmp/dummyfolder/*; tvar"
eval "${ArrayName}"'="${Value}"'
راه پیشگیری از این گونه حفره امنیتی ایجاد تابعی است که اجرایش مقدار معینی از امنیت را به شما میدهد و کُد پاکیزهتری را جایز مینماید.
# check_valid_var_name VariableName
function check_valid_var_name {
case "${1:?Missing Variable Name}" in
[!a-zA-Z_]* | *[!a-zA-Z_0-9]* ) return 3;;
esac
}
# set_variable VariableName [<Variable Value>]
function set_variable {
check_valid_var_name "${1:?Missing Variable Name}" || return $?
eval "${1}"'="${2:-}"'
}
set_variable "laksdpaso" "dasädöas# #-c,c pos 9302 1´ "
set_variable "echo rm -rf /tmp/dummyfolder/*; tvar" "dasädöas# #-c,c pos 9302 1´ "
# return Error
توجه: set_variable یک مزیت اضافه بر کاربرد declare دارد. مورد پایین را ملاحظه کنید.
VariableName="Name=hhh"
declare "${VariableName}=Test Value" # Valid code, unexpected behavior
set_variable "${VariableName}" "Test Value" # return Error
چند مثال دیگر برای ارجاع
# get_array_element VariableName ArrayName ArrayElement
function get_array_element {
check_valid_var_name "${1:?Missing Variable Name}" || return $?
check_valid_var_name "${2:?Missing Array Name}" || return $?
eval "${1}"'="${'"${2}"'["${3:?Missing Array Index}"]}"'
}
# set_array_element ArrayName ArrayElement [<Variable Value>]
function set_array_element {
check_valid_var_name "${1:?Missing Array Name}" || return $?
eval "${1}"'["${2:?Missing Array Index}"]="${3:-}"'
}
# unset_array_element ArrayName ArrayElement
function unset_array_element {
unset "${1}[${2}]"
}
# unset_array_element VarName ArrayName
function get_array_element_cnt {
check_valid_var_name "${1:?Missing Variable Name}" || return $?
check_valid_var_name "${2:?Missing Array Name}" || return $?
eval "${1}"'="${#'"${2}"'[@]}"'
}
# push_element ArrayName <New Element 1> [<New Element 2> ...]
function push_element {
check_valid_var_name "${1:?Missing Array Name}" || return $?
local ArrayName="${1}"
local LastElement
eval 'LastElement="${#'"${ArrayName}"'[@]}"'
while shift && [ $# -gt 0 ] ; do
eval "${ArrayName}"'["${LastElement}"]="${1}"'
let LastElement+=1
done
}
# pop_element ArrayName <Destination Variable Name 1> [<Destination Variable Name 2> ...]
function pop_element {
check_valid_var_name "${1:?Missing Array Name}" || return $?
local ArrayName="${1}"
local LastElement
eval 'LastElement="${#'"${ArrayName}"'[@]}"'
while shift && [[ $# -gt 0 && ${LastElement} -gt 0 ]] ; do
let LastElement-=1
check_valid_var_name "${1:?Missing Variable Name}" || return $?
eval "${1}"'="${'"${ArrayName}"'["${LastElement}"]}"'
unset "${ArrayName}[${LastElement}]"
done
[[ $# -eq 0 ]] || return 8
}
# shift_element ArrayName [<Destination Variable Name>]
function shift_element {
check_valid_var_name "${1:?Missing Array Name}" || return $?
local ArrayName="${1}"
local CurElement=0 LastElement
eval 'LastElement="${#'"${ArrayName}"'[@]}"'
while shift && [[ $# -gt 0 && ${LastElement} -gt ${CurElement} ]] ; do
check_valid_var_name "${1:?Missing Variable Name}" || return $?
eval "${1}"'="${'"${ArrayName}"'["${CurElement}"]}"'
let CurElement+=1
done
eval "${ArrayName}"'=("${'"${ArrayName}"'[@]:${CurElement}}")'
[[ $# -eq 0 ]] || return 8
}
# unshift_element ArrayName <New Element 1> [<New Element 2> ...]
function unshift_element {
check_valid_var_name "${1:?Missing Array Name}" || return $?
[ $# -gt 1 ] || return 0
eval "${1}"'=("${@:2}" "${'"${1}"'[@]}" )'
}
# 1000 x { declare "laksdpaso=dasädöas# #-c,c pos 9302 1´ " } 0m0.069s مدت زمان اجرا
# 1000 x { set_variable laksdpaso "dasädöas# #-c,c pos 9302 1´ " } 0m0.141s مدت زمان اجرا
# 1000 x { get_array_element TestVar TestArray 1 } 0m0.199s مدت زمان اجرا
# 1000 x { set_array_element TestArray 1 "dfds edfs fdf df" } 0m0.174s مدت زمان اجرا
# 1000 x { set_array_element TestArray 0 } 0m0.167s مدت زمان اجرا
# 1000 x { get_array_element_cnt TestVar TestArray } 0m0.171s مدت زمان اجرا
# با یک آرایه دو هزار عنصری اتجام شدهاند push,pops,shifts,unshifts همه نوابع
# 1000 x { push_element TestArray "dsf sdf ss s" } 0m0.274s مدت زمان اجرا
# 1000 x { pop_element TestArray TestVar } 0m0.380s مدت زمان اجرا
# 1000 x { unshift_element TestArray "dsf sdf ss s" } 0m9.027s مدت زمان اجرا
# 1000 x { shift_element TestArray TestVar } 0m5.583s مدت زمان اجرا
توجه، shift_element و unshift_element کارایی ضعیفی دارند و مخصوصاً روی آرایههای بزرگ باید از آنها اجتناب گردد. مابقی کارایی قابل قبولی دارند و من به طور منظم آنها را به کار میبرم.
پرسش و پاسخ 48 (آخرین ویرایش 2013-03-07 03:51:23 توسط ChrisJohnson)
یک لوله فقط میتواند خروجی استاندارد (stdout) برنامه را حمل کند. برای عبور دادن خروجی استاندارد خطا (stderr) از میان لوله، لازم است stderr را به همان مقصد stdout تغییر مسیر بدهید. به طور اختیاری برای دریافت stderr تنها، میتوانید stdout را ببندید یا به /dev/null تغییر مسیر بدهید. چند نمونه کُد:
# Bourne # مینویسد stdout و هم در stderr برنامهایست که هم در 'myprog' فرض کنید # را به لوله تغییر مسیر میدهد درحالیکه stderr نگارش اول # خروجی استاندارد نیز باقی میماند. (هردو مخلوط میشوند) myprog 2>&1 | grep ... # به لوله تغییر مسیر داده میشود، بدون stderr نگارش دوم # .تغییرمسیر داده میشود /dev/null که به stdout حفظ کردن myprog 2>&1 >/dev/null | grep ... # .در یک فایل stdout همان ایده، این دفعه با ذخیره myprog 2>&1 >file | grep ...
یک مثال ساده دیگر از تغییر مسیر stdout و stderr:
# Bourne
{ command | stdout_reader; } 2>&1 | stderr_reader
برای توضیحات بیشتر در مورد چگونگی عملکرد متقابل تغییر مسیرها و لولهها پرسش و پاسخ شماره 55 را ببینید.
این مورد کاربرد واضحی با برنامههایی از قبیل dialog دارد، که(با استفاده از ncurses) پنجرهها را در نمایشگر(خروجی استاندارد) رسم میکند و نتایج را به stderr بر میگرداند. یک روش کار با این برنامه میتواند تغییر مسیر stderr به یک فایل موقتی باشد. اما این ضروری نیست -- پرسش و پاسخ شماره 40 را برای مثالهای استفاده از dialog ببینید!
در مثال فوق(همچنین پرسش و پاسخ شماره 40)، ما به طور کلی stdout را رها کردیم، یا آن را به دستگاه شناخته شدهای فرستادیم(/dev/tty برای ترمینال کاربر). همچنین ممکن است کسی فقط stderr را لولهکشی نماید اما تعامل با stdout را حفظ نماید(بدون شناخت مستدل از جایی که خروجی اسکریپت میرود). این اندکی ماهرانه است.
# Bourne # بدون تغییر stdout به لوله و نگاه داشتنstderr تغییر مسیر exec 3>&1 # ذخیره مقدار فعلی خروجی استاندارد myprog 2>&1 >&3 | grep ... # 3 شماره FD ارسال خروجی استاندارد به exec 3>&- # .حالا بستن آن برای باقیمانده اسکریپت # http://www.tldp.org/LDP/abs/html/io-redirection.html با تشکر از
همان کار میتواند بدون exec انجام بشود:
# POSIX
$ myfunc () { echo "I'm stdout"; echo "I'm stderr" >&2; }
$ { myfunc 2>&1 1>&3 3>&- | cat > stderr.file 3>&-; } 3>&1
I'm stdout
$ cat stderr.file
I'm stderr
توصیفگر شماره 3 بسته میشود (3>&-) بنابراین فرمانها از آن ارث نمیبرند. توجه کنید bash دونسخهای نمودن و بستن در یک تغییر مسیر را اجازه میدهد: 1>&3-
میتوانید در لینوکس با آزمایش زیر تفاوتها را کنترل کنید:
# Bash
{ bash <<< 'lsof -a -p $$ -d1,2,3' ;} 3>&1
{ bash <<< 'lsof -a -p $$ -d1,2,3' 3>&- ;} 3>&1
نمایش dialog یک سطری:
# Bourne exec 3>&1 dialog --menu Title 0 0 0 FirstItem FirstDescription 2>&1 >&3 | sed 's/First/Only/' exec 3>&-
این یک پنجره dialog خواهد داشت که به طور صحیح کار میکند، بازهم خروجی استاندارد dialog خواهد بود(بازگردانده به stderr) که توسط sed جایگزین میشود.
یک نتیجه مشابه میتواند با جایگزینی پردازش حاصل شود:
# Bash perl -e 'print "stdout\n"; warn "stderr\n"' 2> >(tr '[:lower:]' '[:upper:]')
این مثال خطای استاندارد را به میان فرمان tr لولهکشی میکند.
این آموزش تغییر مسیر را ببینید(با یک مثال که stdout را به یک لوله تغییر مسیر میدهد و stderr را به لوله دیگری).
پرسش و پاسخ 47 (آخرین ویرایش 2012-03-05 11:30:04 توسط pgas)