آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

شرط‌ها و بررسی‌ها



شرط‌ها و بررسی‌ها

اجرای ترتیبی فرمانها به جای خود، اما برای دستیابی به منطق پیشرفته در اسکریپت‌هایتان یا در خط فرمان یک جمله‌ای، به شرط‌ها و بررسی‌ها نیاز دارید. بررسی‌ها تعیین می‌کنند که یک مطلبی صحیح است یا غلط. شرط‌ها برای تصمیم‌سازی در مورد انجام فرامینی در اسکریپت به کار می‌روند.


1. وضعیت خروج

از هر دستور موقعی‌که خاتمه می‌یابد یک کد خروج حاصل می‌شود. این کد خروج توسط هر برنامه‌ای که آن دستور را اجرا نموده برای تعیین آنکه مقصودش به درستی انجام شده یا نه استفاده می‌شود. این کد خروج مشابه مقدار برگشتی از توابع می‌باشد. این کد یک عدد صحیح از صفر تا ۲۵۵ می‌باشد. مطابق قرارداد از صفر برای مشخص نمودن موفقیت استفاده می‌کنیم، وهر عدد دیگر بیانگر نوعی شکست می‌باشد. هر برنامه معینی، عدد خاصی را برای اشاره به آنکه دقیقاً چه اشتباهی رخ داده به کار می‌برد.

به عنوان مثال، دستور ping بسته‌های ICMP را در شبکه برای یک میزبان معین ارسال می‌کند. به طور معمول آن میزبان، با برگشت دادن دقیق همان بسته پاسخ می‌دهد. به این طریق می‌توانیم کنترل کنیم که آیا می‌توانیم یک ارتباط با میزبان راه دور برقرار کنیم. دستورping دامنه‌ای از کدهای خروج دارد که اگر مشکلی باشد، می‌تواند به ما بگوید، چه چیز نادرست است:

از مستندات ping لینوکس:

  • اگر ping هیچ بسته بازگشتی دریافت نکند، با کد 1 خارج خواهد شد. اگر یک شماره بسته و یک محدوده زمانی تعیین شده باشد، و شمارش بسته‌های دریافتی در زمان تعیین شده با عدد کمتری اعلام شود نیز با کد 1 خارج می‌شود. در سایر موارد خطا با کد 2 خارج می‌شود. در غیر اینصورت با کد صفر خارج می‌شود. و استفاده از کد خروج امکان آن را فراهم می‌کند که ببینیم میزبان فعال می‌باشد یا خیر.

پارامتر ویژه ? کد خروج آخرین پردازش پیش‌زمینه خاتمه یافته را به ما می‌دهد. اجازه دهید برای دیدن کدهای خروج فرمان ping مثال‌هایی بزنیم:

    $ ping God
    ping: unknown host God
    $ echo $?
    2
    $ ping -c 1 -W 1 1.1.1.1
    PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
    --- 1.1.1.1 ping statistics ---
    1 packets transmitted, 0 received, 100% packet loss, time 0ms
    $ echo $?
    1


  • تکرارمفید:
    همواره باید مطمئن شوید که اسکریپت شما در صورت وقوع رخداد ناخواسته در جریان اجرایش، کد خروج غیر صفر برمی‌گرداند. می‌توانید با استفاده از دستور داخلی exit این کار را عملی کنید:

         rm file || { echo 'Could not delete file!' >&2; exit 1; }



  • کد خروج / وضعیت خروج: هنگامی که یک دستور خاتمه می‌یابد به والدش( در موقعیت ما همیشه پوسته‌ای می‌شود که شروع کرده‌ایم) ، وضعیت خروج خود را گزارش می‌کند. این وضعیت با یک عدد از صفر تا ۲۵۵ نمایانده می‌شود. این کد اشاره‌ای به موفقیت اجرای دستور است.


2. عملگرهای کنترلی (&& و ||)

حال که می‌دانیم کدهای خروج چیستند، و یک کد خروج صفر به معنای اجرای موفق یک دستور می‌باشد، استفاده از این اطلاعات را خواهیم آموخت. ساده‌ترین روش انجام یک عمل معین بر اساس موفقیت دستور قبلی از راه به کارگیری عملگرهای کنترلی می‌باشد. این عملگرها && و || می‌باشند، که به ترتیب یک AND و یک OR منطقی را نمایندگی می‌کنند. این عملگرها بین دو دستور به کار می‌روند، و برای کنترل آنکه آیا دستور دوم بر مبنای موفقیت دستور اول اجرا بشود، استفاده می‌شوند. این مفهوم اجرای شرطی نامیده می‌شود.

بیایید این مطلب را در عمل به کار ببریم:

    $ mkdir d && cd d

این مثال ساده دو دستور دارد، mkdir d و cd d. می‌توانستید از یک سمی‌مالن در آنجا برای جدا کردن دستورهاو اجرای ترتیبی آنها استفاده کنید، اما ما چیزی بیش از آن می‌خواهیم. در مثال فوق، BASH فرمان mkdir d را اجرا می‌کند، سپس && نتیجه برنامه mkdir پس از اتمامش را بررسی می‌کند. اگر برنامه mkdir موفق بود(کد خروج صفر)، بعد Bash دستور بعدی cd d را اجرا می‌کند. اگر mkdir d ناموفق باشد، و یک کد خروج غیر صفر برگرداند، Bash از اجرای دستور بعدی صرفنظر می‌کند، و در دایرکتوری جاری خواهد ماند.

مثالی دیگر:

    $ rm /etc/some_file.conf || echo "I couldn't remove the file"
    rm: cannot remove `/etc/some_file.conf': No such file or directory
          I couldn't remove the file

|| خیلی مشابه && می‌باشد، امادقیقاً مخالف آن عمل می‌کند. فقط موقعی دستور بعدی اجرا می‌شود که دستور اول ناموفق شود. به این ترتیب، پیغام فقط در صورتی که فرمان rm ناموفق باشد، نمایش داده می‌شود.

به طور کلی، متصل کردن چند دستور کنترلی در یک جمله منفرد ایده خوبی نیست(ما این مطلب را در بخش بعدی باز خواهیم کرد). && و || در حالت‌های ساده کاملاً سودمند می‌باشند، اما در وضعیت‌های پیچیده اینطور نیست. در چند بخش بعدی برخی ابزارها که می‌توانید در تصمیم سازی به کار ببرید رانشان خواهیم داد.


  • تکرارمفید:
    وقتی با عبارت‌های شرطی سر و کار دارید خیلی هواخواه این عملگرها نباشید. اینها میتوانند درک اسکریپت شما را دشوار سازند، به ویژه برای کسی که به نگهداری آن منصوب شده و خودش اسکریپت را ننوشته است.



  • عملگرهای کنترل: این عملگرها برای پیوند زدن دستورها با یکدیگر استفاده می‌شوند. آنها کد خروج دستور قبلی را برای تعیین اجرا یا عدم اجرای دستور بعدی بررسی می‌کنند.


3. گروه‌بندی دستورات

استفاده از عملگرهای شرطی ساده و موجز می‌باشد، به شرطی که بخواهیم کنترل خطای ساده‌ای انجام دهیم. گرچه، موقعی که بخواهیم در صورت صحیح بودن یک شرط، جملات چندگانه‌ای را اجرا کنیم، یا نیاز به بررسی شرط‌های چندگانه داشته باشیم، مسائل قدری خطرناک‌تر می‌شوند.

فرض کنید می‌خواهید یک فایل را در صورت وجود کلمه معین "good" در آن و نیز عدم وجود کلمه مشخص "bad" در آن حذف کنید. با استفاده از grep ( فرمانی که ورودی‌اش را برای الگوهای تعیین شده بررسی می‌کند)، این شرایط را به این صورت ترجمه می‌کنیم:

grep -q goodword "$file" #exit status 0 (success) if "$file" contains 'goodword'
! grep -q "badword" "$file" #exit status 0 (success) if "$file" does not contain 'badword'

ما از گزینه ‎-q‎‏(quiet)‏ با فرمان grep استفاده کردیم چون نمی‌خواهیم موارد انطباق را به خروجی ارسال کند، فقط می‌خواهیم کد خروج را تنظیم کند.

علامت ! در جلوی دستور موجب می‌شود Bash وضعیت خروج فرمان را نفی کند. اگر دستور صفر(موفقیت) را برگرداند، کاراکتر ! آن را به عدم موفقیت تبدیل می‌کند، و برعکس، اگر کد غیر صفر(عدم موفقیت) برگرداند، کاراکتر ! آن را به موفقیت تبدیل نماید.

حال برای متصل کردن این شرط‌ها به یکدیگر و ربط دادن حذف فایل به موفقیت هر دو، می‌توانستیم از عملگرهای شرطی استفاده کنیم:

    $ grep -q goodword "$file" && ! grep -q badword "$file" && rm "$file"

این بخوبی کار می‌کند( در حقیقت می‌توانیم هر تعداد از &&ها که می‌خواهیم بدون مشکل با هم زنجیر کنیم ). حالا فرض کنید می‌خواهیم در حالتی که حذف فایل ناموفق باشد، یک پیغام خطا نمایش بدهیم:

    $ grep -q goodword "$file" && ! grep -q badword "$file" && rm "$file" || echo "Couldn't delete: $file" >&2

این هم ظاهراً در نگاه اول صحیح است. اگر کد خروج rm برابر 0(موفقیت) نباشد،سپس عملگر || ماشه اجرای دستور بعدی را می‌ کشد و echo پیغام خطا را نمایش می‌دهد(‎>&2‎ در خروجی استاندارد خطا).

اما مشکلی وجود دارد. موقعی که ما یک توالی از دستوراتی که با عملگرهای شرطی از یکدیگر جدا شده‌اند، داریم Bash به ترتیب از چپ به راست به هر یک از آنها نگاه می‌کند. وضعیت خروج آخرین دستور اجرا شده ثابت می‌کاند و پرش از روی دستورات بعدی آن را تغییر نمی‌دهد.

همچنین تصور کنید اولین grep ناموفق است(کد وضعیت یک می‌شود) . Bash حالا && بعدی را می‌بیند، بنابراین به طور کلی دومین grep را نادیده می‌گیرد. بعد یک && دیگر می‌بیند، بنابراین از دستور rm که بعد از آن است نیز عبور می‌کند. عاقبت یک عملگر || می‌بیند. آها! وضعیت خروج ناموفق است، و ما یک عملگر || داریم، پس Bash دستور echo را اجرا می‌کند، و به ما می‌گوید که نمی‌تواند فایل را حذف کند ولواینکه هرگز اقدام به این عمل نکرده است! و این چیزی نیست که ما می‌خواهیم.

وقتی فقط پیغام خطای اشتباهی دریافت می‌کنید انعکاس خیلی بدی ندارد، اما اگر دقیق نباشید، سرانجام در کدهای خطرناک‌تر، این اتفاق خواهد افتاد. شما که نمی‌خواهید به طور تصادفی در اثر نارسایی منطق برنامه خود فایلی را حذف یا رونویسی نمایید!

نقص منطق ما در این واقعیت است که ما می‌خواهیم فرمان‌های rm و echo وابسته به یکدیگر باشند. دستور echo مربوط به rm می‌باشد، نه مربوط به grepها. بنابراین آنچه ما لازم داریم، گروه‌بندی آنها است. گروه‌بندی با استفاده از ابروها انجام می‌گردد:

    $ grep -q goodword "$file" && ! grep -q badword "$file" && { rm "$file" || echo "Couldn't delete: $file" >&2; }

(توجه: فراموش نکنید که قبل ازبستن ابرو یک سمی‌کالن یا سطر جدید لازم است!)

حالا دستورات rm و echo را با هم گروه‌بندی نموده‌ایم. این به طورمؤثر و کارامدی به معنای آنست که گروه به عنوان یک جمله در نظر گرفته می‌شود، نه چند دستور. برگردیم به موقعیتی که اولین دستور grep ما ناموفق بود، حالا BASH به جای اینکه به جمله ‎&& rm "$file"‎ رسیدگی کند، جمله ‎&& { ... }‎ را بررسی می‌کند. چون یک عملگر && مقدم بر این جمله است و آخرین دستور اجرا شده ناموفق بوده(دستور ناموفق grep)، از روی این گروه عبور کرده به پیش می‌رود.

گروه‌بندی دستورات برای موارد بیشتری غیر از عملگرهای شرطی نیز می‌تواند به کار رود. ممکن است بخواهیم دستورات را گروه‌بندی کنیم تا یک ورودی را به این گروه تغییر مسیر بدهیم، نه فقط به یکی از دستورات:

    {
        read firstLine
        read secondLine
        while read otherLine; do
            something
        done
    } < file

در اینجا ما file را به ورودی یک گروه از دستورات تغییر مسیر داده‌ایم. وقتی اجرای گروه دستورات شروع می‌گردد، فایل باز می‌شود و درتمام مدت اجرا باز می‌ماند و پس از اتمام اجرای گروه بسته می‌شود. به این طریق، می‌توانیم به طور متوالی توسط دستورات چندگانه سطرهای آن را بخوانیم.

یک مورد استفاده رایج دیگر از گروه‌بندی، مدیریت خطای ساده است:

    cd "$appdir" || { echo "Please create the appdir and try again" >&2; exit 1; }


4. بلوک‌های شرطی( if و test و ‎[[‎ )

if یک کلمه‌کلیدی پوسته است که یک دستور( یا یک مجموعه دستور) را اجرا می‌کند، وکد خروج آن دستور را بررسی می‌کند که ببیند آیا موفق شده است. بر مبنای کد خروج، دستور if یک بلوک مشخص، متفاوت، از دستورات را اجرا می‌کند.

    $ if  true
    > then echo "It was true."
    > else echo "It was false."
    > fi
    It was true.

در اینجا یک نمای کلی اساسی از if -statement ملاحظه می‌کنید.با فراخوانی دستور if با true شروع کرده‌ایم. true یک دستور داخلی است که همیشه به طور موفق خاتمه می‌یابد. if این دستور داخلی را اجرا می‌کند، و موقعیکه دستور اجرا شد، if کد خروج آن را بررسی می‌کند. چون true همواره به طور موفق خارج می‌شود، if با بلوک then ادامه می‌دهد، و کد را اجرا می‌کند. اگر به فرض دستور true به طریقی ناموفق می‌شد، و یک کد خروج عدم موفقیت صادر می‌کرد، دستور if از روی کد then عبور کرده و در عوض، کد بلوک else را اجرا می‌نمود.

افراد مختلف شیوه‌های متفاوتی از نوشتن جملات if را ترجیح می‌دهند. در اینجا برخی شیوه‌های رایج را می‌آوریم:

    if  commands
    then other commands
    fi
   -------------------
    if  commands
    then
        other commands
    fi
   -------------------
    if  commands; then
        other commands
    fi

چند دستور وجود دارد که به طور ویژه برای بررسی موارد و بازگرداندن وضعیت خروج نسبت به آنچه تشخیص می‌دهند، طراحی گردیده‌اند. اولین دستور از این قبیل test می‌باشد( که ‎ ‎[‎‎ نیز شناخته می‌شود.) یک نگارش پیشرفته‌تر آن ‎[[‎ نامیده می‌شود(مترجم: برای راحت خواندن این قسمت ‎[‎ را تست و ‎[[‎ را تست جدید بخوانید). ‎[‎ یا test یک دستور معمولی است که شناسه‌هایش را می‌خواند و برخی کنترل‌ها را با آنها انجام می‌دهد. ‎[[‎ خیلی مشابه ‎[‎ است، اما این استثنایی(یک کلمه‌کلیدی پوسته) می‌باشد که تنوع و تطبیق‌پذیری بیشتری ارائه می‌کند.بیایید به کار ببریم:

    $ if [ a = b ]
    > then echo "a is the same as b."
    > else echo "a is not the same as b."
    > fi
    a is not the same as b.

if دستور ‎[‎ را(به خاطر داشته باشید، که نیازی به یک if برای اجرای دستور ‎[‎ ندارید!)با شناسه‌های a و = و b و ] اجرا می‌کند. دستور ‎[‎ این شناسه‌ها را برای تعیین آنچه باید بررسی شود، به کار می‌برد. در این حالت، بررسی می‌کند که آیا رشته a (شناسه اول) مساوی(شناسه دوم) است با رشته b (شناسه سوم)، و اگر چنین باشد، به طور موفق خارج می‌شود. اگرچه، ما می‌دانیم که اینطور نیست، ‎[‎ به طور موفق خارج نمی‌شود(کد خروج آن 1 خواهد بود). if می‌بیند که دستور ‎[‎ به طور ناموفق خاتمه یافته است پس کد بلوک else را اجرا می‌کند.

حال ببینیم که چرا ‎[[‎ خیلی بیش از ‎[‎ جالب و مورد اعتماد است، اجازه دهید برخی مسائل محتمل با ‎[‎ را مشخص نماییم:

    $ myname='Greg Wooledge' yourname='Someone Else'
    $ [ $myname = $yourname ]
    -bash: [: too many arguments

می‌توانید حدس بزنید چه مشکلی موجب بروز خطا شده؟

دستور ‎[‎ با شناسه‌های Greg و Wooledge و = و Someone و Else و ] اجرا گردیده است. اینها ۶ شناسه هستند نه ۴ تا! دستور ‎[‎ نمی‌فهمد که اجرای چه آزمونی مورد انتظار است، زیرا انتظار دارد یکی از دو شناسه اول یا دوم، یک عملگر باشد. در وضعیت ما، عملگر سومین شناسه است. باز هم دلیل دیگری برای چرایی اهمیت شگرف نقل‌قولی نمودن. هنگامی که در Bash فضای سفیدی تایپ می‌کنیم که متعلق به کلمات قبل یا بعد آن می‌باشد، لازم است آن را نقل‌قولی کنیم، و همین طور هم برای بسط پارامترها:

    $ [ "$myname" = "$yourname" ]

در این حالت ‎[‎ دومین شناسه را یک عملگر(=) می‌بیند و می‌تواند به کارش ادامه دهد.

برای کمی مساعدت با ما، پوسته Korn یک سبک جدید بررسی شرطی را معرفی نموده(و BASH نیز آن را اخذ کرده). مؤلف اصل اینها که ‎[[‎ نامیده می‌شوند، پوسته کورن است. در ‎[[‎ چند ویژگی بسیار جالب گنجانیده شده است که در ‎[‎ غایب بودند.

یکی از ویژگیهای ‎[[‎ انطباق الگو است:

    $ [[ $filename = *.png ]] && echo "$filename looks like a PNG file"

ویژگی دیگر ‎[[‎ کمک به ما در ارتباط با بسط پارامترها می‌باشد:

    $ [[ $me = $you ]]           # Fine.
    $ [[ I am $me = I am $you ]] # Not fine!
    -bash: conditional binary operator expected
          -bash: syntax error near `am'

در این حالت، نیازی به نقل‌قولی کردن ‎$me‎ و ‎$you‎ نیست. چون ‎[[‎ یک دستور معمولی نیست(آن طور که‎[‎ هست)، بلکه یک کلمه کلیدی‌ shell می‌باشد، و قدرت جادویی مخصوصی دارد. این کلمه کلیدی شناسه‌هایش را قبل از اینکه آنها توسط Bash بسط داده شوند، تفکیک می‌کند و خودش بسط را انجام می‌دهد و نتیجه را به عنوان یک شناسه منفرد می‌گیرد، حتی اگر این نتیجه شامل فضای سفید هم باشد. (به بیان دیگر ‎[[‎ تفکیک کلمه روی شناسه‌هایش را اجازه نمی‌دهد.) به هرحال، هنوز هم مراقب باشید که رشته‌های ساده به طور صحیحی نقل‌قولی بشوند. زیرا ‎[[‎ نمی‌تواند تشخیص بدهد که آیا فضای سفید درجمله تعمدی است یا خیر، بنابراین آنها را مطابق روشی که BASH به طورمعمول انجام می‌دهد، تفکیک می‌کند. اجازه بدهید مثال را تصحیح کنیم:

    $ [[  "I am $me" = "I am $you" ]]

همچنین، تفاوت ظریف زیرکانه‌ای بین نقل‌قولی کردن و نکردن سمت راست مقایسه در ‎[[‎ وجود دارد. عملگر = وقتی طرف راست نقل‌قولی نشده باشد، به طور پیش‌فرض، انطباق الگو را انجام می‌دهد:

    $ foo=[a-z]* name=lhunath
    $ [[ $name = $foo ]] && echo "Name $name matches pattern $foo"
    Name lhunath matches pattern [a-z]*
    $ [[ $name = "$foo" ]] || echo "Name $name is not equal to the string $foo"
    Name lhunath is not equal to the string [a-z]*

بررسی اول کنترل می‌کند که آیا ‎$name‎ با الگوی محتوای ‎$foo‎ مطابقت دارد. دومین بررسی کنترل می‌کند که آیا ‎$name‎ مساوی رشته محتوای ‎$foo‎ می‌باشد. نقل‌قول‌ها به طور واقعی اختلاف آنها را خیلی زیاد نموده‌اند--سزاوار زیرکی نیست.

یادآوری: اگر اطمینان ندارید، همواره نقل‌قولی کنید. اگر foo واقعاً به جای یک رشته شامل یک الگو باشد(موردی که خواستن آن نادر است -- به طور معمول الگوی شما به طور لفظی نوشته می‌شود: [[ ‎$name‎ = ‎[a-z]*‎ ]])، در اینجا شما یک خطای بی‌خطر دریافت می‌کنید و می‌توانید بیایید و آن را تصحیح کنید. اگر در نقل‌قول کردن سهل‌انگاری کنید، پیدا کردن باگ‌هامی‌تواند بسیار مشکل بشود، چون کد معیوب می‌تواند به طور فوری خرابی را بروز ندهد.

می‌توانید چندین دستور if را هم با استفاده از elif به جای else در یک جمله ترکیب کنید، که در آن هر بررسی نشانگر یک احتمال دیگر باشد:

    $ name=lhunath
    $ if [[ $name = "George" ]]
    > then echo "Bonjour, $name"
    > elif [[ $name = "Hans" ]]
    > then echo "Goeie dag, $name"
    > elif [[ $name = "Jack" ]]
    > then echo "Good day, $name"
    > else
    > echo "You're not George, Hans or Jack.  Who the hell are you, $name?"
    > fi

حال که درک مناسبی از مسائلی که با نقل‌قولها ممکن است ایجاد شود به دست آورده‌اید، بیایید به سایر ویژگی‌هایی که ‎[‎ و ‎[[‎ با آنها پر بار شده‌اند، نگاه کنیم:

  • بررسی‌هایی که با ‎[‎ ( که به عنوان test نیزشناخته می‌شود) پشتیبانی می‌شود:

    • ‎-e FILE‎: اگر فایل موجود باشد صحیح است.

    • ‎-f FILE‎: اگر فایل موجود معمولی باشد صحیح است.

    • ‎-d FILE‎: اگر فایل یک دایرکتوری باشد صحیح است.

    • ‎-h FILE‎: اگر فایل یک پیوند نمادین باشدصحیح است.

    • ‎-r FILE‎: اگر فایل برای شما قابل خواندن باشد صحیح است.

    • ‎-s FILE‎: اگر فایل موجود باشد وتهی نباشد صحیح است.

    • ‎-t FD ‎: اگر FD(توصیف‌گر فایل) در یک ترمینال باز شده باشد صحیح است.

    • ‎-w FILE‎: اگر فایل برای شما قابل نوشتن باشد صحیح است.

    • ‎-x FILE‎: اگر فایل برای شما قابل اجرا باشد صحیح است.

    • ‎-O FILE‎: اگر فایل به طور مؤثر در مالکیت شما باشد صحیح است.

    • ‎-G FILE‎: اگر فایل به طور مؤثر در مالکیت گروه شما باشد صحیح است.

    • ‎FILE -nt FILE‎: اگر فایل اول جدیدتر از فایل دوم باشد صحیح است.

    • ‎FILE -ot FILE‎: اگر فایل اول قدیمی‌تر از فایل دوم باشد صحیح است.

    • ‎-z STRING‎: اگر رشته تهی باشد(طول آن صفر باشد) صحیح است.

    • ‎-n STRING‎: اگر رشته تهی نباشد(طول آن صفر نباشد) صحیح است.

    • ‎STRING = STRING‎: اگر رشته اول از هر نظر مانند دومی باشد صحیح است.

    • ‎STRING != STRING‎: اگر رشته اول دقیقاً مانند رشته دوم نباشد صحیح است.

    • ‎STRING < STRING‎:اگر در مرتب‌سازی رشته اول قبل از دومی قرار می‌گیرد صحیح است.

    • ‎STRING > STRING‎: اگر رشته اول در مرتب‌سازی بعد از رشته دوم قرارمی‌گیرد صحیح است.

    • ‎EXPR -a EXPR‎: اگر هر دوعبارت صحیح باشندصحیح است(and منطقی).

    • ‎EXPR -o EXPR‎: اگر هر یک از دو عبارت صحیح باشد صحیح است(or منطقی).

    • ‎! EXPR‎: نتیجه عبارت را معکوس می‌کند( NOTمنطقی).

    • ‎INT -eq INT‎: اگر هر دو عدد صحیح دقیقاً برابر باشند صحیح است.

    • ‎INT -ne INT‎: اگر هر دو عدد صحیح دقیقاً برابر نباشند، صحیح است.

    • ‎INT -lt INT‎: اگر عدد صحیح اولی کوچکتر از دومی باشد صحیح است.

    • ‎INT -gt INT‎: اگر عدد صحیح اولی از دومی بزرگتر باشد صحیح است.

    • ‎INT -le INT‎: اگر عدد صحیح اولی کوچکتر یا مساوی دومی باشد صحیح است.

    • ‎INT -ge INT‎: اگر عدد صحیح اولی بزرگتر یا مساوی دومی باشد صحیح است.

  • بررسی‌های اضافی که فقط توسط ‎[[‎ پشتیبانی می‌شوند :

    • ‎STRING = (or ==) PATTERN‎: مانند ‎[‎ (یا test) مقایسه نمی‌کند، بلکه انطباق الگو انجام می‌شود. اگر رشته با الگوی جانشین منطبق گردد، صحیح است.

    • ‎STRING =~ REGEX‎: اگر رشته با الگوی regex(عبارت منظم)تطبیق کند، صحیح است.

    • ‎( EXPR )‎: پرانتزها می‌توانند برای تغییر اولویت ارزیابی‌ها به کار بروند.

    • ‎EXPR && EXPR‎: خیلی مشابه عملگر ‎ -a ‎  در test می‌باشد، اما اگر نتیجه عبارت اول صحیح نباشد، عبارت دوم ارزیابی نمی‌شود.

    • ‎EXPR || EXPR‎: خیلی مشابه عملگر ‎ -o ‎  در test می‌باشد، اما اگر نتیجه عبارت اول صحیح باشد، عبارت دوم ارزیابی نمی‌شود.

چند مثال؟ حتماً:

    $ test -e /etc/X11/xorg.conf && echo 'Your Xorg is configured!'
    Your Xorg is configured!
    $ test -n "$HOME" && echo 'Your homedir is set!'
    Your homedir is set!
    $ [[  boar != bear ]] && echo "Boars aren't bears."
    Boars aren't bears!
    $ [[  boar != b?ar ]] && echo "Boars don't look like bears."
    $
    $ [[  $DISPLAY ]] && echo "Your DISPLAY variable is not empty, you probably have Xorg running." 
    Your DISPLAY variable is not empty, you probably have Xorg running.
    $ [[  ! $DISPLAY ]] && echo "Your DISPLAY variable is not not empty, you probably don't have Xorg running."
    $


  • تکرارمفید:
    هنگامی که یک اسکریپت BASH ایجاد می‌کنید، همیشه باید از ‎[[‎ به جای ‎[‎ استفاده کنید.
    وقتی یک اسکریپت پوسته می‌نویسید، که پس از اتمام ممکن است در محیطی که BASH در دسترس نباشد، به کار برود، باید از ‎[‎ استفاده کنید، به دلیل آنکه به مراتب قابل حمل‌تر می‌باشد. ( در حالیکه در BASH و برخی پوسته‌های دیگر، ‎[‎ یک دستور داخلی است، به صورت یک برنامه خارجی نیز به خوبی در دسترس می‌باشد، یعنی به عنوان شناسه مثلاً exec و xargs کار خواهد کرد.)
    هرگز از ‎ -a ‎ یا ‎ -o ‎ در بررسی‌های فرمان ‎[‎ استفاده نکنید. به جای آن از فرمان‌های چندگانه ‎[‎ ( یا اگر می‌توانید از ‎[[‎ ) استفاده کنید. استاندارد POSIX رفتار ‎[‎ در مجموعه بررسی‌های پیچیده را تعریف نکرده، بنابراین هرگز نمی‌دانید چه رفتاری حاصل می‌شود.

        if  [ "$food" = apple ] && [ "$drink" = tea ]; then
          echo "The meal is acceptable."
        fi




  • if (کلمه‌کلیدی): لیستی از دستورات را اجرا می‌کند و سپس نسبت به کد خروج آنها، کد بلوک then ( بخش اختیاری else) را اجرا می‌نماید.


5. حلقه های شرطی( while و until و for)

تا اینجا آموخته‌اید چگونه برخی تصمیم‌گیری های اساسی در اسکریپت‌هایتان را بسازید. اگر چه، برای انجام همه انواع وظایفی که ممکن است از اسکریپت بخواهیم کافی نمی‌باشد. گاهی اوقات نیاز به تکرار برخی کارها داریم. برای همین، کاربرد یک حلقه لازم است. دو نوع اصلی ازحلقه( به اضافه نوع دیگری) وجود دارد، و استفاده از نوع صحیح حلقه به شما در نگهداری خوانایی و قابلیت پشتیبانی اسکریپت‌هایتان کمک می‌کند.

دو نوع اساسی حلقه‌ها while و for می‌باشند. حلقه while نوع دیگری دارد که until نامیده شده، که به سادگی بررسی را برعکس انجام می‌دهد، و حلقه for در دو قالب متفاوت ظاهرمی‌شود. در اینجا خلاصه‌ای از آنها:

  • while command: تا وقتی که command به طور موفقی اجرا می‌شود(کد خروج صفر است)، تکرار می‌شود.

  • until command: مادامی‌که command به طور ناموفق اجرا گردد(کد خروج صفر نباشد)، تکرار می‌شود.

  • for variable in words: حلقه برای هر یک از wordsکه به نوبت در متغیر variable قرار می‌گیرند، تکرار می‌شود.

  • for (( expression; expression; expression ))‎: با اجرای اولین عبارت حسابی شروع می‌کند، تا موقعیکه ارزیابی دومین عبارت حسابی موفق است حلقه تکرار می‌شود، و در پایان هر حلقه عبارت حسابی سوم انجام می‌شود.

هر شکل از حلقه‌ها با کلمه‌کلیدی do دنبال می‌شود، پس از آن یک یا چند فرمان در بدنه، بعد هم کلمه‌کلیدی done. کلمه‌کلیدی های do و done مشابه then و fi ( و elif یا else احتمالی) در ساختار دستور if که قبلاً دیدیم، می‌باشند. کار آنها این است که به ما بگویند حلقه از کجا شروع و به کجا ختم می‌شود.

در عمل، حلقه‌ها برای انواع مختلفی از وظایف به کار می‌روند. حلقه for (شکل اول) برای موقعی مناسب است که لیستی داریم، و می‌خواهیم عناصر آن را به طور ترتیبی به کار ببریم. حلقه while مناسب وقتی است که به طور دقیق نمی‌دانیم چند مرتبه عملی باید تکرار شود، و می‌خواهیم تا رسیدن به موردی که در انتظار آن هستیم تکرار انجام شود.

در اینجا چند مثال برای تشریح تفاوتها و همچنین شباهت‌های حلقه‌ها می‌آوریم. (یادآوری: در اکثر سیستم‌عامل‌ها، برای کشتن برنامه‌ای که در ترمینال در حال اجرا است از ترکیب کلیدی ‎Ctrl-C‎ استفاده می‌شود.)

    $ while true
    > do echo "Infinite loop"
    > done

    $ while ! ping -c 1 -W 1 1.1.1.1; do
    > echo "still waiting for 1.1.1.1"
    > sleep 1
    > done

    $ (( i=10 )); while (( i > 0 ))
    > do echo "$i empty cans of beer."
    > (( i-- ))
    > done
    $ for (( i=10; i > 0; i-- ))
    > do echo "$i empty cans of beer."
    > done
    $ for i in {10..1}
    > do echo "$i empty cans of beer."
    > done

سه حلقه آخری با ترکیب متفاوت، دقیقاً به نتیجه یکسانی می‌رسند. در تجربه اسکریپت‌نویسی شل خود بارها با این مورد مواجه می‌شوید. تقریباً همیشه راهکارهای چندگانه‌ای برای حل یک مسئله موجود است. به زودی تشحیص مهارت شما در حل مسئله نخواهد بود، آنقدر که درچگونگی بهترین روش حل آن خواهد بود. شما باید بیاموزید که بهترین زاویه رویکرد حل مسئله را انتخاب کنید. به طور معمول، سادگی و قابلیت انعطاف از عوامل اصلی کد نویسی خوب محسوب می‌شوند. مورد مطلوب شخصی من، آخرین مثال است. در آن نمونه من بسط ابرو را برای تولید کلمات به کار برده‌ام، اما روشهای دیگری نیز وجود دارد.

بیایید نگاه نزدیک‌تری به آن مثال آخری داشته باشیم، زیرا اگر چه از دو حلقه for به نظر آسان‌تر می‌آید، اما، اگر به طور دقیق ندانید که چگونه عمل می‌کند، غالباً می‌تواند فریب دهنده باشد.

به طوری که قبلاً اشاره کرده‌ام: for لیستی از کلمات را گرفته و هر یک از آنها را در متغیر شاخص حلقه می‌گذارد، هر دفعه یکی، و سپس بدنه حلقه را با آن اجرا می‌کند. قسمت فریب دهنده آن است که BASH چطور تصمیم می‌گیرد که کلمات کدام هستند. به من اجازه دهید خودم با بسط ابروها در مثال قبلی شرح بدهم:

    $ for i in 10 9 8 7 6 5 4 3 2 1
    > do echo "$i empty cans of beer."
    > done

BASH کاراکترهای بین کلمه‌کلیدی in و انتهای سطر را می‌گیرد، و آنها را به کلمات تفکیک می‌نماید. این تفکیک نسبت به فاصله و tabها انجام می‌شود، درست مانند تفکیک شناسه‌ها. اما اگر هر جایگزینی نقل‌قولی نشده‌ای آنجا باشد، آن هم به کلمات تفکیک می‌گردد(با استفاده از محتوای متغیر IFS). تمام این کلمات تفکیک شده، عناصر تکرار می‌شوند.

در نتیجه، خیلی مراقب باشید که اشتباه زیر را مرتکب نشوید:

    $ ls
    The best song in the world.mp3
    $ for file in $(ls *.mp3)
    > do rm "$file"
    > done
    rm: cannot remove `The': No such file or directory
          rm: cannot remove `best': No such file or directory
          rm: cannot remove `song': No such file or directory
          rm: cannot remove `in': No such file or directory
          rm: cannot remove `the': No such file or directory
          rm: cannot remove `world.mp3': No such file or directory

شما از قبل نسبت به نقل‌قولی کردن ‎$file‎ در دستور rm آگاه بودید، اما در اینجا چه چیزی اشتباه است؟ BASH جایگزینی دستور (‎$(ls *.mp3)‎) را بسط می‌دهد، آن را با خروجی دستور تعویض می‌کند، و بعد تفکیک کلمه را روی آن انجام می‌دهد(به علت آنکه نقل‌قولی نیست). در واقع Bash این عبارت را اجرا می‌کند for file in The best song in the world.mp3.
Boom, مات شدید.

خواهید گفت، آن را نقل‌قولی می‌کنم؟ اجازه دهید فایل دیگری اضافه کنم:

    $ ls
    The best song in the world.mp3  The worst song in the world.mp3
    $ for file in "$(ls *.mp3)"
    > do rm "$file"
    > done
    rm: cannot remove `The best song in the world.mp3  The worst song in the world.mp3': No such file or directory

نقل‌قول‌ها به راستی از فضای سفید در نام فایل‌های شما محافظت می‌کنند، اما چیزی بیش از آن انجام می‌دهند. نقل‌قول‌ها از تمام فضاهای سفید خروجی فرمان ls محافظت خواهند کرد. راهی وجود ندارد که BASH بتواند تشخیص بدهد کدام بخشهای خروجی فرمان ls نام فایل‌ها را نمایندگی می‌کنند. خروجی فرمان ls یک رشته ساده است، و BASH با آن به همین عنوان رفتار می‌کند. بعد for تمام خروجی نقل‌قولی شده را در متغیر i قرار می‌دهد و دستور rm را با آن اجرا می‌کند. لعنت، دوباره مات شدید.

بنابراین چه کار بکنیم؟ به طوری که قبلاً پیشنهاد نمودم، جانشین‌ها بهترین دوست شما هستند:

    $ for file in *.mp3
    > do rm "$file"
    > done

حالا، BASH می‌داند که با نام فایل‌ها سروکار دارد، و نام فایها را می‌شناسد، و بنابراین به طور مطلوبی آنها راتفکیک می‌کند. نتیجه بسط جابشین چنین است: for ‎file in "The best song in the world.mp3" "The worst song in the world.mp3"‎. مشکل رفع شد!

حالا بیایید به حلقه while بپردازیم. حلقه while به خاطر ظرفیتش در اجرای دستورات تا موقع وقوع موضوع مورد نظر، خیلی جالب است. در اینجا چند مثال که نشان می‌دهد چگونه حلقه while غالباً به کار می‌رود :

    $ # ماشین نوشیدنی، نوشیدنی‌ها را در ازای بهای ۲۰ سنت تحویل می‌دهد
    $ while read -p $'The sweet machine.\nInsert 20c and enter your name: ' name
    > do echo "The machine spits out three lollipops at $name."
    > done

    $ # هر پنج دقیقه یکبار ایمیل شما را بررسی می‌کند
    $ while sleep 300
    > do kmail --check
    > done

    $ # برای برخط(آنلاین) شدن مجدد میزبان منتظر می‌ماند
    $ while ! ping -c 1 -W 1 "$host"
    > do echo "$host is still unavailable."
    > done; echo -e "$host is available again.\a"

حلقه until خیلی به ندرت استفاده می‌شود، فقط به علت آنکه تا حد بسیار زیادی مشابه حلقه while  می‌باشد. می‌توانستیم آخرین مثال را با حلقه until به صورت زیربنویسیم:

    $ # برای برگشت میزبان به حالت آماده برقراری ارتباط منتظر می‌ماند
    $ until ping -c 1 -W 1 "$host"
    > do echo "$host is still unavailable."
    > done; echo -e "$host is available again.\a"

در عمل، اکثر مردم حقیقتاً از حلقه while  به جای آن استفاده می‌کنند.

بالإخره، از دستور داخلی continue برای پرش به جلو بدون اجرای بقیه بدنه و اجرای دور بعدی تکرار حلقه، و دستور داخلی break برای پریدن به خارج از حلقه و ادامه دستورات پس از حلقه در اسکریپت، می‌توانید استفاده کنید. این دستورات با هر دو حلقه for و while کار می‌کنند.




  • حلقه: یک حلقه ساختاری است که برای تکرار کد تا موقع تحقق یک وضعیت معین، طراحی می‌شود. در آن نقطه حلقه متوقف می‌شود و کد بعد از آن اجرا می‌گردد.

  • for (کلمه‌کلیدی): یک حلقه for نوعی حلقه است که یک متغیر را به ترتیب، معادل عناصر لیستی از کمیت‌ها قرار می‌دهد، و بدنه را با آن متغیر اجرا می‌کند، و تا تمام شدن لیست تکرار می‌کند.

  • while (کلمه‌کلیدی): یک حلقه while نوعی حلقه است که اجرای کد را تا موقعی که یک دستور معین(قبل از هر تکرار اجرامی‌شود) به طور موفق اجرا می‌شود، ادامه می‌دهد.

  • until (کلمه‌کلیدی): یک حلقه until نوعی حلقه است که اجرای کد را تا موقعی که یک دستور معین(قبل از هر تکرار اجرامی‌شود) به طور ناموفق اجرا می‌شود، ادامه می‌دهد..


6. انتخاب‌ها(case و select)

گاهی اوقات می‌خواهید برنامه‌ای منطقی بر مبنای محتوای یک متغیر بسازید. این می‌توانست با گرفتن انشعاب‌های مختلف یک جمله if بر اساس نتایج حاصل از بررسی یک glob پیاده‌سازی شود:

    shopt -s extglob

    if  [[  $LANG = en* ]]; then
        echo 'Hello!'
    elif [[  $LANG = fr* ]]; then
        echo 'Salut!'
    elif [[  $LANG = de* ]]; then
        echo 'Guten Tag!'
    elif [[  $LANG = nl* ]]; then
        echo 'Hallo!'
    elif [[  $LANG = it* ]]; then
        echo 'Ciao!'
    elif [[  $LANG = es* ]]; then
        echo 'Hola!'
    elif [[  $LANG = @(C|POSIX) ]]; then
        echo 'hello world'
    else
        echo 'I do not speak your language.'
    fi

اما این همه مقایسه یک مقدار زائد است. BASH یک کلمه‌کلیدی به نام case دقیقاً برای چنین وضعیت‌هایی فراهم نموده است. یک جمله case اساساً چندین احتمال الگوهای جانشین را به شمار می‌آورد و محتوای پارامتر شما را نسبت به آنها بررسی می‌کند:

    case $LANG in
        en*) echo 'Hello!' ;;
        fr*) echo 'Salut!' ;;
        de*) echo 'Guten Tag!' ;;
        nl*) echo 'Hallo!' ;;
        it*) echo 'Ciao!' ;;
        es*) echo 'Hola!' ;;
        C|POSIX) echo 'hello world' ;;
        *)   echo 'I do not speak your language.' ;;
    esac

هر انتخاب در جمله case شامل یک الگو( یا لیستی از الگوها با کاراکتر | بین آنها)، یک پرانتز سمت راست، یک بلوک کد که در صورت انطباق رشته با یکی از نمونه‌ها باید اجرا بشود، و دو کاراکتر سمی‌کالن برای علامت‌گذاری انتهای کد( چون ممکن است لازم شود کد را در چند سطر بنویسید). case انطباق نمونه‌ها را موقعی‌که یک مورد موفق است، متوقف می‌کند. سپس می‌توانیم ازالگوی * در انتها برای انطباق با هرحالت دیگری که موارد انتخاب ذکر شده را در بر نمی‌گیرد، به کار ببریم. و در پایان نیز کلمه‌کلیدی esac.

یک ساختار دیگر برای انتخاب، ساختار select می‌باشد. که مشابه یک حلقه است، این جمله‌ای برای سهولت ایجاد منویی از انتخابها می‌باشد، که کاربر می‌تواند از میان آنها گزینش کند .

به کاربر انتخابهایی ارائه می‌شود و از او درخواست می‌شود یک عدد منعکس کننده انتخابش را وارد کند کند. سپس کد داخل بلوک select با متغیری که با انتخاب کاربر تنظیم می‌شود اجرا می‌شود. اگر انتخاب کاربر معتبر نباشد، محتوای متغیر تهی می‌شود:

    $ echo "Which of these does not belong in the group?"; \
    > select choice in Apples Pears Crisps Lemons Kiwis; do
    > if  [[  $choice = Crisps ]]
    > then echo "Correct!  Crisps are not fruit."; break; fi
    > echo "Errr... no.  Try again."
    > done

تا موقعی که دستور break اجرا نشده است، منو باز تولید می‌شود. در این مثال دستور break فقط وقتی اجرا می‌شود که کاربر مورد صحیح را انتخاب نماید.

همچنین می‌توانیم از متغیر PS3 برای تعیین اعلانی که کاربر به آن پاسخ می‌دهد استفاده کنیم. به جای نشان دادن پرسش قبل از اجرای جمله select، می‌توانیم تنطیم سؤال را به عنوان اعلان انتخاب کنیم:

    $ PS3="Which of these does not belong in the group (#)? " \
    > select choice in Apples Pears Crisps Lemons Kiwis; do
    > if  [[  $choice = Crisps ]]
    > then echo "Correct!  Crisps are not fruit."; break; fi
    > echo "Errr... no.  Try again."
    > done

تمام این ساختارهای شرطی(if و for و while و case) می‌توانند تو در تو بشوند. این به آن معناست که می‌توانید یک حلقه for با یک حلقه while در داخل آن داشته باشید، یا هر ترکیب دیگر با هرچقدر تودرتویی که مسئله شما را حل کند.

    # یک منوی ساده
    while true; do
        echo "Welcome to the Menu"
        echo "  1. Say hello"
        echo "  2. Say good-bye"

        read -p "-> " response
        case $response in
            1) echo 'Hello there!' ;;
            2) echo 'See you later!'; break ;;
            *) echo 'What was that?' ;;
        esac
    done


تکرارمفید:
جمله select ساختن منوی ساده را آسان می‌کند، اما انعطاف‌پذیری بیشتری ارائه نمی‌کند. اگر چیزی استادانه‌تر خواسته باشید، شایدترجیح بدهید منوی خودتان را با کاربرد یک حلقه while بنویسید، با چند دستور echo یا printf ، و یک دستور read.




  • case(کلمه‌کلیدی): جمله case مقدار یک پارامتر را نسبت به چند الگوی داده شده(انتخاب‌ها) ارزیابی می‌کند.

  • select(کلمه‌کلیدی): جمله select انتخاب چند گزینه را به کاربر پیشنهاد می‌کند و بلوک کد مربوط با انتخاب کاربر در یک پارامتر اجرا می‌کند. منو تا وقتی که یک دستور break اجرا گردد تکرار می‌شود.

نظرات 0 + ارسال نظر
ایمیل شما بعد از ثبت نمایش داده نخواهد شد