شما باید \[ و \] را در اطراف هر یک از رشتههای escape غیرقابل چاپ در اعلان خود قرار بدهید. از این قرار:
fancy_prompt() { local blue=$(tput setaf 4) local purple=$(tput setaf 5) local reset=$(tput sgr0) PS1="\[$blue\]\h:\[$purple\]\w\[$reset\]\\$ " }
بدون \[ \]، bash گمان خواهد کرد بایت هایی که رشتههای escape برای کُدهای رنگ را تشکیل میدهند به طور واقعی فضایی را در نمایشگر اشغال خواهند نمود، بنابراین bash قادر نخواهد بود بداند که اشارهگر واقعاً کجا میباشد.
اگر شما بازهم مشکل دارید، به عنوان مثال، موقعی که تاریخچه فرمانها را با کلیدهای جهتی بالا و پایین مرور میکنید، اطمینان حاصل نمایید که گزینه checkwinsize تنظیم باشد:
shopt -s checkwinsize
به فصلANSI escape codes ویکی پدیا مراجعه نمایید.
به طور کلیتر، شما باید از نوشتن رشتههای escape ترمینال به طور مستقیم در اعلان خود اجتناب نمایید، زیرا آنها لزوماً در میان تمام ترمینالهایی که اکنون یا در آینده استفاده خواهید نمود قابل حمل نیستند. از tput برای تولید رشتههای صحیح برای ترمینال خود استفاده کنید( این برنامه به بانک اطلاعاتی terminfo یا termcap شما نگاه میکند).
چون tput یک فرمان خارجی است، هر چند مرتبه که لازم باشد، شما باید آن را اجرا کنید، به این علت پیشنهاد میکنیم نتایج آن را در متغیرها ذخیره کنید، و (به جای استفاده از $(tput ...) به طور مستقیم در PS1، که در هر نوبتی که اعلان نمایش داده میشود tput را اجرا میکند)، آنها را در ساختار اعلان به کار ببرید. به این ترتیب کُد تشکیل دهنده اعلان برای خواندن آسانتر از خود اعلان میباشد، و درطیف متنوعی از ترمینالها کار میکند. (بعضی ترمینالها شاید ویژگیهایی که سعی میکنید به کار ببرید را نداشته باشند ، از قبیل رنگها، بنابراین نتایج در وضعیتهای پیچیده به اندازه 100% قابل حمل نیستند. اما میتوانید نزدیک شوید.)
یادداشت شخصی: بازهم من این جواب را ترجیح میدهم:
BLUE=$(tput setaf 4) PURPLE=$(tput setaf 5) RESET=$(tput sgr0) PS1='\[$BLUE\]\h:\[$PURPLE\]\w\[$RESET\]\$ '
من درک میکنم که اشخاص میخواهند از مخدوش شدن متغیر namespace زیرنویس 1 اجتناب کنند، بنابراین از تابع و بخش local استفاده میکنند، که به ترتیب استفاده از نقلقول دوگانه و نیاز به دوتایی نمودن بعضی از
imadev:~$ FOO='\w'; PS1='$FOO\$ ' \w$ FOO='\w'; PS1="$FOO\\$ " ~$
فرض کنیم ترمینال ما از \w در یک رشته escape استفاده میکند. یک \w داخل متغیری که در PS1 نقلقولی شده منفرد رجوع میشود، وقتی اعلان چاپ گردد فقط به \w لفظی بسط داده میشود، که آنچه ما میخواهیم است. اما در نگارش نقلفول دوگانه ، \w که به طور مستقیم داخل متغیر PS1 گمارده شده، موقع چاپ اعلان، توسط bash ارزیابی میشود. حال، من واقعاً ترمینالی را که از چنین نشانهای استفاده کند نمیشناسم --این یک ایراد کاملاً نظری است. اما از طرف دیگر، ایراد وارده به استفاده از متغیرهایی مانند BLUE نیز همینطور است. و به هر حال برخی اشخاص شاید واقعاً بخواهند در پوستههایشان از echo "$BLUE" استفاده کنند. بنابراین من نمیخواهم بگویم پاسخ نقلقول منفرد بهتر است، بلکه دوست داشتم ببینم که به عنوان یک جایگزین در اینجا حفظ میشود. -- GreyCat
کاملاً صحیح. من در ابتدا قصد داشتم فقط BLACK= را به RESET= تغییر بدهم(چون همه از سفید روی سیاه استفاده نمیکنند)، اما بعد اندیشیدم اگر اعلان به متغیرهایی که متغیر هستند وابسته نباشد، بهتر خواهد شد. من به طور واضح در مورد احتمال چنین رشتههای escape ترمینال آگاه نبودم، بنابراین فکر میکنم ذکر کردن نگارش اول نقلقول منفرد، میتوانست ایده بهتری باشد و همچنین یادآوری آنکه اگر آن متغیرها تغییر کنند چه اتفاقی میافتد.
من گمان میکنم شخص میتوانست برای پیشگیری از تغییر تصادفی متغیرها و ضایع شدن اعلان فرمان، آنها را فقط خواندنی نیز بنماید، اگر چه احتمالاً آن نیز اشکالات دیگری خواهد داشت..؟
پرسش و پاسخ 53 (آخرین ویرایش 2012-03-27 20:46:02 توسط GreyCat)
در اینجا تداخل کلمات به کار رفته برای رنگها با نام متغیرها، مورد نظر GreyCat است.
(1)
در برخی سیستمها برای علامت زدن انتهای سطر، کاراکترهای رفتن سر سطر(CR)به کار میروند. سه نوع مختلف انتهای سطر رایج است:
اگر شما در حال اجرای اسکریپتی در سیستم یونیکس میباشید، لازم است انتهای سطرها یونیکسی باشد(فقط LFها)، در غیر آنصورت مشکل خواهید داشت.
یک بازرسی ساده، فقط نگاه کردن به خروجی cat -e است:
cat -e yourscript
اگر چیزی مانند این را میبینید، آنوقت با سطرهای جدید سَبکِ CRLF سر و کار دارید:
command^M$ ^M$ another command^M$
یک شیوه دیگر، استفاده از برنامه سودمند file برای پی بردن به نوع فایل است:
file yourscript
اگر فایل دارای CR باشد خروجی به شما میگوید که متن ASCII داری CR میباشد. توجه: این روش فقط در گنو-لینوکس صادق است. در سایر سیستمعاملها، نتیجه file غیرقابل پیشبینی است، به استثنای آنکه اگر نوع فایل مانند فایل متن باشد کلمه "text" در جایی از نتیجه خروجی ظاهر میشود.
imadev:~$ printf 'DOS\r\nline endings\r\n' > foo imadev:~$ file foo foo: commands text arc3:~$ file foo foo: ASCII text, with CRLF line terminators
در یک اسکریپت، گفتن آنکه کدام شیوه، قابل اعتمادترین روش خواهد بود، مشکلتر است. هر کدام که انجام بدهید، غیر مستدل خواهد بود. در تئوری، یک فایل غیر معیوب تولید شده توسط برنامه سودمند یونیکسی به سامان، تنها باید شامل LFها، و تولید شده توسط یک برنامه DOS، نباید شامل LF های ساده بدون یک CR مقدم، باشد. میتوانید این مورد را با یک PCRE بررسی کنید.
# Bash و Ksh در پوسته # [[ $(</dev/fd/0) == ~(P)(?<!$'\r')$'\n' ]] # ksh93 PCRE pattern=*[^$'\r']$'\n'* if [[ $(</dev/fd/0) == $pattern ]] <<<$'foo\r\nbar\r\nbaz\r'; then print 'File contains only CRLFs' else print 'File contains at least one newline not preceded by a CR' fi
ex یک روش استاندارد مناسب برای تبدیل CRLF به LF میباشد، و احتمالاً یکی از چند روش قابل اعتماد انجام آن به طور یکجا از داخل یک اسکریپت است:
ex -sc $'%s/\r$//e|x' file
البته، هر یک از زبانهای پویای قدرتمندتر برای انجام این کار با سهولت نسبی.
perl -pi -e 's/\r\n/\n/' filename
برخی سیستمها دارای ابزارهای معتبر ویژه تبدیل، جهت انجام این کار به طور خودکار، هستند. dos2unix، recode، و fromdos برخی نمونهها میباشند.
به طور دستی با ویرایشگری مانند nano انجام میشود:
nano -w yourscript
Ctrl-O را تایپ کنید و قبل از تأیید، Alt-D (DOS) یا Alt-M (Mac) را برای تغییر قالب تایپ نمایید.
یا در Vim از :set fileformat=unix استفاده کنید و با :w ذخیره کنید. مراقبت کنید که مقدار fenc صحیح باشد (احتمالاً utf-8).
برای آنکه به سادگی تمام CRها را از جریان ورودی پاک کنید، میتوانید از tr -d '\r' <infile >outfile استفاده نمایید. البته، شما باید مطمئن باشید که نام این فایلها یکسان نباشد.
پرسش و پاسخ 52 (آخرین ویرایش 2013-02-10 15:48:58 توسط ormaaj)
فقط سطرهای زیر را در فایل /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)