تجزیه کننده Bash - آموزش اسکریپت نویسی
X
تبلیغات
رایتل

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

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

#!/bin/bash

تجزیه کننده Bash

BashParser

تجزیه کننده Bash

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

  • مرحله 1: خواندن داده‌ها برای اجرا.

    • Bash همیشه اسکریپت یا فرمان شما در اعلان فرمان bashرا سطر به سطر می‌خواند. اگر سطر شما با یک کاراکتر \ خاتمه یابد، bash قبل از پردازش فرمان یک سطر دیگر را می‌خواند و آن را به سطر جاری ضمیمه می‌کند، با یک سطر جدید لفظی بین آنها. (من از اینجا به بعد قطعه‌ای از داده‌ها را که Bash می‌خواند به عنوان سطرِ داده‌ها، ذکر خواهم نمود، ولواینکه از نظر تکنیکی ممکن است شامل یک یا چند سطر جدید باشد.)

      • مرحله ورودی:
        echo "What's your name?"read name; echo "$name"
        مرحله خروجی:
        echo "What's your name?"

        و
        read name; echo "$name"

  • مرحله 2: پردازش نقل‌قولها.

    • یکبار که Bash سطر داده‌های شما را خواند، برای یافتن نقل‌قولها به دقت آن را نگاه می‌کند. اولین نقل‌قولی که پیدا می‌کند یک وضعیت نقل‌قولی را تا رسیدن به نقل‌قول بعدی از همان نوع، برای تمام کاراکترهای پس از آن راه‌اندازی می‌کند. اگر وضعیت نقل‌قولی با نقل‌قول دوگانه راه‌اندازی گردد ‎("...")‎، تمام کاراکترها غیر از $ و " و \ هر گونه معنای خاصی را که ممکن است داشته باشند از دست می‌دهند. این مطلب نقل‌قولهای منفرد، فاصله‌ها و سطرجدید و غیره را شامل می‌گردد. اگر حالت نقل‌قولی با نقل‌قول منفرد راه‌اندازی گردیده باشد ‎('...')‎، تمام کاراکترها به استثنای ' معنای ویژه خود را از دست می‌دهند. بله، $ و \ نیز به همچنین. بنابراین، فرمان پایین خروجی لفظی ارائه خواهد نمود:

         $ echo 'Back\Slash $dollar "Quote"'
         Back\Slash $dollar "Quote"
      واقعیت آنکه \ توانایی خود برای لغو کردن معنی کاراکتر بعدی را از دست می‌دهد، به معنای آن است که عمل نخواهد کرد:
          $ echo 'Don\'t do this'
          >

      Bash از شما سطر بعدی ورودی را درخواست می‌کند زیرا برخلاف آنچه ما گمان می‌کردیم، نقل‌قول دوم، آنکه ما سعی کردیم با کاراکتر گریز \ پوشش بدهیم(مترجم: از معنای ویژه‌اش معاف کنیم)، در واقع وضعیت نقل‌قولی ما را مسدود کرد، یعنی ‎t do this‎ نقل‌قولی نشده بود. سپس آخرین نقل‌قول در این سطر دوباره وضعیت نقل‌قولی ما را باز کرده ، و bash ورودی بیشتر تا بسته شدن دوباره آن را درخواست می‌کند(سعی می‌کند مرحله 1 را به پایان برساند: خواندن داده‌ها را تا رسیدن به سطرجدید معاف نشده، ادامه می‌دهد. موقعیت نقل‌قول منفرد، کاراکتر سطر جدید ما را از معنی خاص معاف کرده). اکنون که bash می‌داند کدام یک از کاراکترها در سطر داده‌ها معاف شده(محروم شدن از قابلیت هر معنای ویژه‌ای برای Bash) و کدام معاف نشده، Bash نقل‌قول‌هایی را که برای تعیین این مورد استفاده شده، از داده‌ها حذف می‌نماید و به مرحله بعدی پیش می‌رود.

      مرحله ورودی:
      echo "What's your name?"
      مرحله خروجی:
      echo What's your name?

      • (توجه: در اصل هر کاراکتر بین نقل‌قول‌های دوگانه به عنوان معاف‌شده علامت‌گذاری شده است. من در این مثالها کاراکترهای معاف شده را با علامتگذاری به صورت مایل مشخص خواهم نمود.)

  • مرحله 3: تفکیک داده‌های خوانده به دستورات.

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

          $ echo "What a lovely day; and sunny, too!"
         What a lovely day; and sunny, too!
      • مرحله ورودی:
        read name; echo $name
        مرحله خروجی:
        read name
        و
        echo $name

  • مراحل زیر برای هر فرمان حاصل شده از تفکیک سطر داده‌ها، اجرا می‌گردد:

  • مرحله 4: تجزیه عملگرهای خاص.

    • نگاه کردن دقیق به هر فرمان برای دیدن آنکه آیاعملگرهای خاصی از قبیل ‎{..}‎، ‎<(..)‎، ‎< ...‎، ‎<<< ..‎، ‎.. | ..‎، و غیره وجود دارد. تمام اینها با ترتیب مخصوصی پردازش می‌شوند. عملگرهای تغییر مسیر از سطر فرمان حذف می‌شوند، سایر عملگرها با عبارت نتیجه شده از آنها جایگزین می‌شوند (یعنی ‎{a..c}‎ توسط ‎a b c‎ جایگزین می‌گردد).

      • مرحله ورودی:
        diff <(foo) <(bar)
        مرحله خروجی:
        diff /dev/fd/63 /dev/fd/62

        • (توجه: عملگر ‎<(..)‎ یک پردازش پس‌زمینه برای اجرای فرمان foo (و یک پردازش نیز برای bar) شروع می‌کند و خروجی را به یک فایل ارسال می‌کند. سپس خودش با نام مسیر آن فایل تعویض می‌شود.)

  • مرحله 5: انجام بسط‌ها.

    • Bash عملگرهای بسیاری دارد که با بسط مربوط می‌شوند. ساده‌ترین اینها ‎ $parameter‎ است. علامت دلارِ دنبال شده با نام یک پارامتر، که به طور اختیاری شاید با ابروها احاطه شده باشد، بسط پارامتر نامیده می‌شود. اساساً آنچه Bash اینجا انجام می‌دهد، فقط تعویض عامل بسط پارامتر با محتویات آن پارامتر است. به همین جهت، فرمان ‎ echo $USER‎ در این مرحله به ‎echo lhunath‎ تبدیل خواهد شد. سایر بسط‌ها شامل بسط نام مسیر (‎echo *.txt‎)، جایگزینی فرمان (‎rm "$(which nano)"‎)، و غیره می‌باشند.

      • مرحله ورودی:

        echo "$PWD has these files that match *.txt :" *.txt

        مرحله خروجی:

        echo /home/lhunath/docs has these files that match *.txt : bar.txt foo.txt

  • مرحله 6: تفکیک فرمان به نام فرمان و شناسه‌ها.

    • نام فرمان Bash که باید اجرا شود همیشه اولین کلمه در سطر است. بقیه داده‌های فرمان به کلماتی نفکیک می‌گردند که شناسه‌ها را به وجود می‌آورند. این فرایند تفکیک کلمه )Word Splitting) نامیده می‌شود. اساساً Bash سطرفرمان را از جایی که فضای سفید می‌بیند به قطعه‌ها تقسیم می‌کند. این فضای سفید کاملاً از بین می‌رود و قطعه‌ها کلمات نامیده می‌شوند. فضای سفید در این زمینه یعنی: همه فاصله‌ها، tabها یا سطرهای جدید که معاف نشده‌اند. (فاصله‌های معاف شده، از قبیل فاصله‌های داخل نقل‌قولها، معنی ویژه فضای سفیدشان را از دست می‌دهند و برای تجزیه سطر فرمان به کار نمی‌روند. آنها به طور لفظی در شناسه‌های حاصله ظاهر می‌گردند.) همینطور، اگر نام فرمانی که می‌خواهید اجرا نمایید یا یکی از شناسه‌هایی که می‌خواهید به آن عبور بدهید شامل فاصله‌هایی باشند که شما نمی‌خواهید bash برای بریدن سطر فرمان به کلمات به کار ببرد، می‌توانید از نقل‌قولها یاکاراکتر \ استفاده نمایید:

        My Command /foo/bar   ## .را اجرا خواهد نمود چون اولین کلمه در این سطر فرمان است‎'My'‎ این فرمانی به نام‎
        "My Command" /foo/bar ## .را اجرا خواهد کرد چون فاصله داخل نقل‌قولها معنای ویژه خود برای تفکیک کلمات را از دست می‌دهد ‎'My Command'‎ این فرمانی به نام 
      • مرحله ورودی:
        echo "/home/lhunath/docs has these files that match *.txt :" bar.txt foo.txt

        مرحله خروجی:
        نام فرمان: ‎'echo'‎
        شناسه 1: ‎'/home/lhunath/docs has these files that match *.txt :'‎
        شناسه 2: ‎'bar.txt'‎
        شناسه 3: ‎'foo.txt'‎

  • Step 7: Execute the command.

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

      • مرحله ورودی:
        sleep 5

        باعث می‌شود:
        ‎ ‎├┬· 33321 lhunath -bash        ‎
        │├──· 46931 lhunath sleep 5

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

مثال گرافیکی

برای یک مثال بیشتر ساده شدهِ پردازش، این صفحه گرافیکی را ببینید.

اشتباهات متداول

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

  • start=1; end=5; for number in {$start..$end}‎: در مرحله 4 بسط ابرو وقوع می‌یابد در حالیکه، در مرحله 5 بسط پارامتر رخ می‌دهد. بسط ابرو سعی می‌کند ‎{$start..$end}‎ را بسط بدهد، اما نمی‌تواند. این بسط، ‎$start‎ و ‎$end‎ را به عنوان رشته می‌بیند، نه بسط پارامتر، و رهایشان می‌کند:

    • نتایج مرحله 4:
      start=1
      end=5
      for number in {1..5}
      نتایج مرحله 5:
      start=1
      end=5
      for number in {$start..$end}

      و number به جای 1 اکنون ‎{1..5}‎ خواهد شد. خیر بسط ابرو انجام نگردیده است.زیرنویس مترجم

  • [ $name = B. Foo ]‎: تفکیک کلمه این مثال را ناموفق می‌سازد. در این حالت برنامه ‎(‎[‎)‎ test چهار شناسه را جستجو می‌کند. طرف سمت چپ، یک عملگر، طرف سمت راست، و یک ‎]‎ انتهایی. برای پی‌بردن به آنکه در این مثال چه چیز اشتباه است، آنطور که Bash انجام می‌دهد ببینید: جدا کردن فرمان به شناسه‌ها. فرض کنیم ‎name‎ شامل ‎B. Foo‎ است:

    • [
    • B.
    • Foo
    • =
    • B.
    • Foo
    • ]
    • یک مشت بیش از چهارتا. لازم است برای آنکه از انجام تفکیک کلمه به موجب فاصله میان ‎B.‎ و Foo پیش گیری گردد، از نقل‌قولها استفاده کنید. نقل‌قول کردن ‎B. Fooو$name‎ به طوری که وقتی ‎$name‎ بسط داده می‌شود، فضای سفید در ‎B. Foo به طور یکسان با آنکه در طرف راست هست رفتار کند. اهمیت دارد که به خاطر بسپارید، مرحله 5 (انجام بسط) قبل از مرحله 6 می‌آید (تفکیک فرمان به نام فرمان و شناسه‌ها). این به معنای آنست که ‎$name‎ از خُرد شدن نتیجه‌اش مصون نمی‌باشد، زیرا خُرد شدن بعد ازاینکه ‎$name‎ با مقدارش در داخل name تعویض می‌شود، به وقوع می‌پیوندد.


CategoryShell

تجزیه کننده Bash (آخرین ویرایش ‎2013-07-10 01:58:19‎ توسط ormaaj)


  1. مترجم: برای وضوح بیشتر من این مثال را به صورت زیر همراه با خروجی بازنویسی نموده‌ام

    #--------------
    #   مرحله4
    #--------------‎
      $start=1
      $end=5
      $for number in {1..5}
      >do
      >	echo $number
      >done
      1
      2
      3
      4
      5          # بسط ابرو انجام شده است‎
      $
    #--------------
    #   مرحله5
    #--------------‎
      $start=1
      $end=5
      $for number in {$start..$end}
      >do
      >	echo $number
      >done
      {1..5}     # فقط بسط پارامترها انجام گردیده است و بسط ابرو انجام نشده‎ 
      $
    
    (بازگشت)