Turandot: Gli enigmi sono tre, la morte una! Caleph: No, no! Gli enigmi sono tre, una la vita! --Puccini |
اینها هم شیوههایی از اسکریپتنویسی (توصیه نشده) هستند که به زندگی یکنواخت بدون حضور خودشان، هیجان میبخشند.
case=value0 #باعث مشکلات میشود. 23skidoo=value1 #این نیز مشکل ساز است. #نام متغیرهایی که با یک رقم شروع میشوند به وسیله پوسته رزرو شدهاند. #_23skidoo=value1 را امتحان کنید. شروع شدن نام متغیرها با یک خط زیر قبول است. #اما . . . استفاده از فقط یک خط زیر تنها کار نخواهد کرد. _=25 echo $_ #$_ یک متغیر ویژه است که معادل آخرین شناسه آخرین فرمان است. #اما . . . _ برای تابع یک نام قابل قبول است! xyz((!*=value2 #باعث مشکلات شدیدی میشود. #از Bash نگارش 3، نقطه در نام متغیرها مجاز نیست.
به کار بردن خط تیره یا سایر کاراکترهای رزرو شده در نام یک متغیر (یا نام تابع).
var-1=23 #به جای آن از var_1 استفاده کنید. function-whatever () #خطا #در عوض، function_whatever () را به کار ببرید. #از نگارش 3 در Bash، نقطه در نام تابع مجاز نیست. function.whatever () #خطا #functionWhatever () را به جای آن استفاده کنید.
استفاده از نام یکسان برای یک متغیر و یک تابع. این کار میتواند فهمیدن اسکریپت را دشوار سازد.
do_something () { echo "This function does something with \"$1\"." } do_something=do_something do_something do_something #تمام اینها مجاز، اما بسیار گیج کننده هستند.
استفاده از فضای سفید به طور نامناسب. در مقایسه با سایر زبانهای برنامهنویسی، Bash میتواند در مورد فضای سفید کاملاً بهانهگیر باشد.
var1 = 23 #var1=23 صحیح است. #در سطر بالا Bash تلاش میکند فرمان var1 را با #شناسههای «=» و «23» اجرا نماید. let c = $a - $b #به جای آن: let c=$a-$b یا let "c = $a - $b" if [ $a -le 5] #if [ $a -le 5 ] صحیح است. #حتی if [ "$a" -le 5 ] بهتر است. #[[ $a -le 5 ]] نیز کار میکند.
خاتمه ندادن فرمان پایانی یک بلوک کد داخل براکتهای کمانی با سمیکالن.
{ ls -l; df; echo "Done." } # { ls -l; df; echo "Done."; } # #فرمان انتهایی سمیکالن لازم دارد.##
فرض کردن آنکه متغیرهای ارزشگذاری نشده (متغیرها قبل از آنکه یک مقدار به آنها تخصیص یابد) صفر هستند. یک متغیر ارزشگذاری نشده دارای کمیت null(تهی) است، نه کمیت صفر.
#!/bin/bash echo "uninitialized_var = $uninitialized_var" # #به هر حال . . . #اگر $BASH_VERSION ≥ 4.2 باشد، آنوقت if [[ ! -v uninitialized_var ]] then uninitialized_var=0 #ارزشگذاری آن به کمیت صفر! fi
اشتباه کردن = و -eq در یک تست. به خاطر داشته باشید که = برای مقایسه متغیرهای لفظی است و -eq برای مقایسه اعداد صحیح است.
if [ "$a" = 273 ] #آیا $a یک عدد صحیح است یا یک رشته؟ if [ "$a" -eq 273 ] #اگر $a یک عدد صحیح باشد. #گاهی اوقات میتوانید بدون پیامدهای نامساعد، -eq و = تعویض کنید. a=273.0 #یک عدد صحیح نیست. if [ "$a" = 273 ] then echo "Comparison works." else echo "Comparison does not work." fi # #همان نتیجه با a=" 273" و a="0273" به دست میآید. #به همچنین، وجود مشکلات در به کار بردن -eq با مقادیر غیر صحیح. if [ "$a" -eq 273.0 ] then echo "a = $a" fi #با یک پیغام خطا بینتیجه میماند. #
استفاده نادرست از عملگرهای مقایسه رشته.
#!/bin/bash #آزمایش به کار بردن یک مقایسه رشتهای روی اعداد صحیح. echo number=1 #حلقه while پایین دارای دو خطا است: #+یکی آشکار، و دیگری نامحسوس. while [ "$number" < 5 ] #اشتباه! باید while [ "$number" -lt 5 ] باشد do echo -n "$number " let "number += 1" done #با شکست مواجه شده و پیغام خطای زیر را صادر میکند: #+ #در درون براکتهای منفرد < باید escape شود و حتی در #+آنصورت باز هم برای مقایسه اعداد صحیح، اشتباه است. echo "---------------------" while [ "$number" \< 5 ] # do # echo -n "$number " #به نظر میرسد کار میکند، اما. . . let "number += 1" #+در حقیقت به جای یک مقایسه عددی، done #+یک مقایسه ASCII انجام میدهد. echo; echo "---------------------" #این میتواند باعث مشکلاتی بشود. برای مثال: lesser=5 greater=105 if [ "$greater" \< "$lesser" ] then echo "$greater is less than $lesser" fi # #در حقیقت، در مقایسه رشتهای (به ترتیب اسکی) #+105 واقعاً کوچکتر از 5 است. echo exit 0
اقدام به استفاده از let جهت تنظیم متغیرهای رشتهای.
let "a = hello, you" echo "$a" #
گاهی اوقات لازم است متغیرهای «test» داخل براکتها ([ ]) نقلقولی (نقلقول دوگانه) بشوند. غفلت در انجام آن ممکن است باعث رفتار غیر منتظرهای بشود. مثال 7-6، مثال 20-5، و مثال 9-6 را مشاهده نمایید.
نقلقول کردن یک متغیر شامل فضای سفید مانع تقسیم شدن آن میشود. این عمل گاهی اوقات پیامدهای ناخواستهای تولید میکند.
اجرای فرمانهای صادر شده از یک اسکریپت ممکن است به علت اینکه مالک اسکریپت فاقد مجوز اجرای آنها باشد، با شکست مواجه گردد. اگر کاربری از خط فرمان نتواند یک فرمان را فراخوانی نماید، آنوقت قرار دادن آن در اسکریپت نیز به شکست منجر خواهد شد. تغییر دادن صفات فرمان مورد بحث، حتی شاید تنظیم بیت suid ( البته، به عنوان root) را امتحان کنید.
اقدام به استفاده از کاراکتر - به عنوان یک عامل تغییر مسیر (که نیست) به طور معمول منجر به یک غافلگیری ناخوشایند میگردد.
command1 2> - | command2 #تلاش برای تغییرمسیر خطای خروجی command1 به یک لوله. . . #. . . کار نخواهد کرد. command1 2>& - | command2 #این هم بیهوده است. #با تشکر از S.C.
استفاده از توانایی Bash نگارش 2+ ممکن است باعث یک دردسر همراه با پیغامهای خطا بشود. ماشینهای لینوکس قدیمیتر ممکن است به عنوان پیشفرض نصب، دارای Bash نگارش 1.XX باشند.
#!/bin/bash minimum_version=2 #چون Chet Ramey به طور مداوم ویژگیهایی به Bash اضافه میکند، شما باید #$minimum_version را به 2.XX، 3.XX یا هر چه که مناسب است تنظیم کنید. E_BAD_VERSION=80 if [ "$BASH_VERSION" \< "$minimum_version" ] then echo "This script works only with Bash, version $minimum or greater." echo "Upgrade strongly recommended." exit $E_BAD_VERSION fi ...
استفاده از تواناییهای مختص Bash در یک اسکریپت پوسته Bourne (#!/bin/sh) روی یک ماشین غیر لینوکس میتواند باعث رفتار غیر منتظره بشود. یک سیستم لینوکس به طور معمول sh را مستعار bash میسازد، اما این مطلب لزوماً برای هر ماشین یونیکسی صحت ندارد.
استفاده از ویژگیهای مستندسازی نشده در Bash میتواند رویه خطرناکی باشد. در انتشارهای قبلی این کتاب چند اسکریپت وجود داشتند که مبتنی بر خصیصهای بودند که اگرچه حداکثر مقدار برگشتی یک exit یا return برابر با 255 بود، اما آن محدودیت برای اعداد صحیح منفی صدق نمیکرد. متاسفانه، در نگارش 2.05b و پس از آن این روزنه ناپدید شد. مثال 24-9 را ببینید.
در برخی مضمونها، ممکن است وضعیت خروج گمراه کنندهای برگشت داده شود. این اتفاق ممکن است موقع تنظیم یک متغیر محلی در درون یک تابع یا هنگام تخصیص دادن یک کمیت حسابی به یک متغیر رخ بدهد.
وضعیت خروج یک عبارت حسابی معادل با یک کد خطا نیست.
var=1 && ((--var)) && echo $var #اینجا لیست and با وضعیت خروج 1 خاتمه مییابد. #$var نمایش داده نمیشود! echo $? #
اجرای اسکریپتی با سطرهای جدید به سبکِ DOS (یعنی \r\n) ناموفق خواهد شد، چون #!/bin/bash\r\n شناخته شده نیست، آنچه مورد انتظار است یعنی #!/bin/bash\n نیست. چاره کار، تبدیل سطرهای اسکریپت به سبکِ یونیکس است.
#!/bin/bash echo "Here" unix2dos $0 #اسکریپت خودش را به قالب DOS تغییر میدهد. chmod 755 $0 #باز گرداندن مجوز اجرا به اسکریپت. #فرمان unix2dos مجوز اجرا را حذف میکند. ./$0 #اسکریپت اقدام به اجرای خود میکند. #اما به عنوان یک فایل DOS کار نمیکرد. echo "There" exit 0
یک اسکریپت پوسته با سرآیند #!/bin/sh در وضعیت سازگاری کامل با Bash کار نمیکند. ممکن است برخی عملکردهای مختص Bash غیر فعال بشوند. اسکریپتهایی که نیاز به دسترسی کامل به تمام الحاقیههای مختص Bash دارند، باید با سطر #!/bin/bash شروع بشوند.
قرار دادن فضای سفید قبل از رشته نگهبانِ خاتمه دهنده یک here document در اسکریپت باعث رفتار غیر منتظره میگردد.
قرار دادن بیش از یک جمله echo در تابعی که خروجیاش ضبط میشود.
add2 () { echo "Whatever ... " #این تابع درست عمل نخواهد کرد.این سطر را حذف کنید! let "retval = $1 + $2" echo $retval } num1=12 num2=43 echo "Sum of $num1 and $num2 = $(add2 $num1 $num2)" # # #به هم پیوستن «echo»ها.
یک اسکریپت نمیتواند متغیرها را به پشت سر خود، پردازش پدر، پوسته، یا محیط، export نماید. همانطور که در بیولوژی آموختهایم، پردازش فرزند میتواند از پدر ارث ببرد، نه برعکس.
WHATEVER=/home/bozo export WHATEVER exit 0
bash$ echo $WHATEVER bash$
همچنانکه پیشبینی میشود، $WHATEVER در اعلان فرمان تنظیم نشده باقی میماند.
تنظیم و دستکاری متغیرها در یک پوسته فرعی، سپس اقدام به استفاده از همان متغیرها خارج از محدوده پوسته فرعی منجر به یک غافلگیری ناخوشآیند میگردد.
مثال 34-2. دامهای پوسته فرعی
#!/bin/bash #دامهای متغیرها در یک پوسته فرعی. outer_variable=outer echo echo "outer_variable = $outer_variable" echo ( #شروع پوسته فرعی echo "outer_variable inside subshell = $outer_variable" inner_variable=inner #تنظیم نمودن متغیر echo "inner_variable inside subshell = $inner_variable" outer_variable=inner #کمیت را به طور سراسری تغییر خواهد داد؟ echo "outer_variable inside subshell = $outer_variable" #آیا export کردن تفاوتی ایجاد میکند؟ # # #امتحان کنید و ببینید. #پایان پوسته فرعی ) echo echo "inner_variable outside subshell = $inner_variable" #تنظیم نشده. echo "outer_variable outside subshell = $outer_variable" #تغییر نیافته. echo exit 0 #اگر سطر 19 و 20 را از حالت توضیح خارج کنید #چه میشود؟ آیا تفاوتی به وجود میآورد؟
لولهکشی خروجی echo به یک read ممکن است نتایج غیر منتظره تولید کند. در این سناریو، read چنانکه گویی در یک پوسته فرعی در حال اجرا است، عمل میکند. به جای آن، فرمان set را (همچون در مثال 15-18) به کار ببرید.
مثال 34-3. لولهکشی خروجی echo به یک فرمان read
#!/bin/bash # #کوشش برای استفاده از echo و read جهت #+تخصیص متغیرها به صورت غیر محاورهای. # a=aaa b=bbb c=ccc echo "one two three" | read a b c #تخصیص دوباره a، b، و c را امتحان کنید. echo echo "a = $a" #a = aaa echo "b = $b" #b = bbb echo "c = $c" #c = ccc #تخصیص مجدد ناموفق گردیده است. #با این وجود. . . #غیر توضیح نمودن سطر 6: # #+مشکل را برطرف میسازد! #این یک ویژگی جدید در Bash نگارش 4.2 است. #--------------------------------------- #جایگزین پایین را امتحان کنید. var=`echo "one two three"` set -- $var a=$1; b=$2; c=$3 echo "-------" echo "a = $a" #a = one echo "b = $b" #b = two echo "c = $c" #c = three #تخصیص مجدد موفق گردیده است. #--------------------------------------- #همچنین توجه نمایید که echo برای یک read داخل پوسته فرعی کار میکند. #اما، مقدار متغیر فقط داخل پوسته فرعی تغییر میکند. a=aaa #شروع دوباره از ابتدا. b=bbb c=ccc echo; echo echo "one two three" | ( read a b c; echo "Inside subshell: "; echo "a = $a"; echo "b = $b"; echo "c = $c" ) #a = one #b = two #c = three echo "-----------------" echo "Outside subshell: " echo "a = $a" #a = aaa echo "b = $b" #b = bbb echo "c = $c" #c = ccc echo exit 0
در حقیقت، همچنانکه Anthony Richardson توضیح میدهد، لولهکشی هر حلقهای میتواند باعث مشکل مشابهی بشود.
#لولهکشی حلقه درد سر ایجاد میکند. #این مثال نوشته Anthony Richardson با #+افزایشهای Wilbert Berendsen است. foundone=false find $HOME -type f -atime +30 -size 100k | while true do read f echo "$f is over 100KB and has not been accessed in over 30 days" echo "Consider moving the file to archives." foundone=true #------------------------------------ echo "Subshell level = $BASH_SUBSHELL" # #بله، ما داخل یک پوسته فرعی هستیم. #------------------------------------ done #foundone در اینجا همواره false خواهد بود #+چون در پوسته فرعی به true تبدیل میشود. if [ $foundone = false ] then echo "No files need archiving." fi ##====================اکنون، این هم روش درست:=================== foundone=false for f in $(find $HOME -type f -atime +30 -size 100k) # No pipe here. do echo "$f is over 100KB and has not been accessed in over 30 days" echo "Consider moving the file to archives." foundone=true done if [ $foundone = false ] then echo "No files need archiving." fi ##====================و این هم یک جایگزین دیگر================= #بخشی از اسکریپت را که متغیرها را میخواند در داخل یک بلوک کد #+قرار میدهد، بنابراین آنها در یک پوسته فرعی شرکت دارند. #با تشکر از W.B. find $HOME -type f -atime +30 -size 100k | { foundone=false while read f do echo "$f is over 100KB and has not been accessed in over 30 days" echo "Consider moving the file to archives." foundone=true done if ! $foundone then echo "No files need archiving." fi }
هنگام اقدام برای نوشتن خروجی استاندارد یک tail -f لولهکشی شده به grep مشکل مشابهی رخ میدهد.
tail -f /var/log/messages | grep "$ERROR_MSG" >> error.log #فایل error.log دارای مورد نوشته شدهای در درونش نخواهد بود. #به طوری که Samuli Kaipiainen اشاره میکند، این مشکل در اثر #+آن است که grep خروجیاش را میانگیری (buffering) میکند. #چاره آن افزودن پارامتر «--line-buffered» به grep است.
به کار بردن فرمان «suid» در داخل اسکریپتها پرمخاطره است، چون ممکن است امنیت سیستم را در معرض خطر قرار بدهد. [1]
استفاده از اسکریپتهای پوسته برای برنامهنویسی CGI ممکن است مشکلآفرین باشد. متغیرهای اسکریپت پوسته «ضامن نوع» نیستند و این تا جاییکه به CGI مربوط است میتواند باعث رفتار نامطلوب بشود. علاوه بر این، «cracker-proof» کردن اسکریپتهای پوسته دشوار است.
Bash رشته اسلاشهای دوتایی (//) را به طور صحیح مدیریت نمیکند.
اسکریپتهای Bash نوشته شده برای سیستمهای لینوکس یا BSD ممکن است برای اجرا بر روی ماشین یونیکس تجاری نیازمند اصلاح کردن باشند. چنین اسکریپتهایی بیشتر اوقات مجموعهای از فرمانها و فیلترهای گنو را به کار میبرند، که دارای تواناییهای بیشتری نسبت به همتاهای نوع یونیکسی آنها هستند. این مطلب به طور خاص در مورد برنامههای پردازش متن از قبیل tr صحت دارد.
با تاسف بسیار، به روز رسانیها برای خود Bash نیز اسکریپتهای قدیمیتری را که کاملا درست کار میکردند، نقض نمودهاند. اجازه بدهید دوباره یادآور شوم استفاده از ویژگیهای مستندسازی نشده Bash چطور پر مخاطره هستند.
Danger is near thee -- Beware, beware, beware, beware. Many brave hearts are asleep in the deep. So beware -- Beware. --A.J. Lamb and H.W. Petrie |
[1] | تنظیم مجوز suid روی خود اسکریپت تاثیری روی لینوکس و اکثر گرایشهای دیگر یونیکس ندارد. |
#!/bin/bash a=2 if [ $a > 12 ] then echo "a is greater than 12" else echo "a is less than 12" fi