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

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

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

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

ورودی و خروجی



ورودی و خروجی

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

ورودی به هر اطلاعاتی که برنامه شما دریافت می‌کند(یا می‌خواند) اشاره می‌نماید. در یک اسکریپت Bash ورودی از چند محل مختلف می‌تواند برسد:

  • شناسه‌های خط فرمان(که در پارامترهای مکانی قرارگرفته‌اند)

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

خروجی به هر اطلاعاتی که برنامه شما ارائه می‌کند(یا می‌نویسد) اشاره می‌کند. خروجی یک اسکریپت Bash نیز می‌تواند به چندین محل مختلف برود:

  • فایلها
  • هر چیز دیگر که یک توصیف‌گر فایل می‌تواند به آن اشاره کند
  • شناسه‌های خط فرمانی برای سایر برنامه‌ها
  • متغیرهای محیطی که به سایر برنامه‌ها ارسال می‌شوند

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


1. شناسه‌های خط فرمان

برای بسیاری از اسکریپت‌ها، اولین(یا تنها) ورودی که به آن توجه می‌نماییم، شناسه‌هایی می‌باشند که اسکریپت در خط فرمان دریافت نموده است. به طوری که در فصل پارامترها دیدیم، تعدادی پارامتر ویژه معتبر برای هر اسکریپت وجود دارد که، محتوی این شناسه‌ها هستند. اینها پارامترهای مکانی نام دارند. این پارامترها یک آرایه خیلی ساده از رشته‌ها می‌باشند که با اعداد شاخص‌گذاری شده‌اند(در حقیقت، در شل POSIX ، تنها آرایه موجود در شل هستند). به اولین پارامتر مکانی با ‎$1‎ رجوع می‌شود، به دومین، با ‎$2‎، و به همین ترتیب، پس از نهمین پارامتر، باید از ابروها برای رجوع به آنها استفاده گردد: ‎${10}, ${11}‎، و غیره. اما در عمل، خیلی به ندرت ممکن است مجبور به استفاده از این ترکیب بشوید، به علت آنکه، روشهای بهتری برای کار با آنها به عنوان یک گروه، موجود است.

علاوه بر ارجاع یک به یک ، همچنین می‌توانید به مجموعه کامل پارامترهای مکانی، با جایگزینی ‎"$@"‎ رجوع نمایید. نقل‌قول دوگانه در اینجا بینهایت مهم است. اگر از نقل‌قول دوگانه استفاده نکنید، هر کدام از پارامترهای مکانی دستخوش تفکیک و جانشینی می‌گردند. شما آن را نمی‌خواهید. با استفاده از نقل‌قول‌ها، به Bash می‌گویید که می‌خواهید هر پارامتر به صورت یک کلمه جداگانه حفظ گردد.

یک روش دیگر کارکردن با پارامترهای مکانی، دور انداختن هر یک پس از استفاده است. یک دستور داخلی ویژه‌ای به نام shift وجود دارد، که برای این منظور به کار می‌رود. موقعی که شما فرمان shift را صادر می‌کنید، اولین پارامتر مکانی (‎$1‎) از بین می‌رود. دومین پارامتر می‌شود ‎$1‎، سومی می‌شود ‎$2‎، و به همیت ترتیب تا پایان خط. بنابراین، اگر مایل باشید، می‌توانید حلقه‌ای بنویسید که استفاده از ‎$1‎ را چندین بار ادامه دهد.

در اسکریپت‌های حقیقی، ترکیبی از این تکنیک‌ها به کار می‌رود. یک حلقه برای پردازش ‎$1‎ مادامی‌که با یک علامت - شروع می‌شود آن را به عنوان گزینه تعبیر می‌کند و سپس وقتی همه گزینه‌ها به کنار رفتند، هر چیز باقیمانده (در ‎"$@"‎) نام فایلی است که می‌خواهیم پردازش کنیم.

برای اختصار، در اینجا مثالهایی از پردازش شناسه‌ها نمی‌آوریم. به جای آن، به FAQ جایی که مثالهای آنها قبلاً نوشته شده ارجاع می‌دهیم.


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



2. محیط

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

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

به طور سنتی، متغیرهای محیط نامهایی تماماً با حروف بزرگ دارند، از قبیل PATH یا HOME. این مطلب به شما کمک می‌کند، از ایجاد متغیرهایی که موجب تصادم با آنها گردد، اجتناب نمایید، شما نباید نگرانی در مورد تصادم اتفاقی با محیط داشته باشید، مشروط به آن که متغیرهای شما، حداقل یک حرف کوچک در نام خود داشته باشند . (متغیرهای ویژه Bash نیز با حروف بزرگ هستند، از قبیل PIPESTATUS. این مورد نیز دقیقاً به همان علت می‌باشد -- به طوری که شما می‌توانید از لگدمال شدن متغیرهایتان توسط Bash، پرهیز کنید.)

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

متغیرهای محیط همچنین می‌توانند به طور بینهایت آسانی در حین کار تنظیم گردند( آسان‌تر از آنکه اگر همان اطلاعات در یک فایل ذخیره شده باشند). موقعی که در Bash دستوری را اجرا می‌کنید، گزینه‌ای دارید، برای تعیین یک تغییر موقتی محیط که فقط در طول مدت اجرای آن فرمان مؤثر است. این با قرار دادن عبارت VAR=value جلوی آن فرمان انجام می‌شود. در اینجا یک مثال آورده‌ایم:

    $ ls /tpm
    ls: no se puede acceder a /tpm: No existe el fichero o el directorio
    $ LANG=C ls /tpm
    ls: cannot access /tpm: No such file or directory

محیط موقتی LANG=C باعث نمی‌شود که منطقه کاربر برای مورد دیگری غیر از آن فرمانی که بعد از آن تایپ گردیده است، تغییر نماید.

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

    if [[ $DISPLAY ]]; then
        xterm -e top
    else
        top
    fi

این مثال، در صورتی که متغیر محیطی DISPLAY تنظیم شده باشد( و تهی نباشد ) دستور ‎ xterm -e top را اجرا می‌کند، در غیر آن صورت، دستور top را اجرامی‌نماید.

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

    export MYVAR=something

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

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


  • تکرار مفید:
    در اسکریپت‌های خود از نامهای تماماً با حروف بزرگ برای متغیرها استفاده نکنید. برای پرهیز از تصادمات، حروف کوچک یا ترکیبی از کوچک و بزرگ به کار ببرید .



3. توصیف‌گرهای فایل

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

به طور پیش‌فرض، هر فرایند جدیدی با سه FD آغاز می‌شود. به این توصیف‌گرهای فایل با نام‌های ورودی استاندارد، خروجی استاندارد و خطای استاندارد رجوع می‌شود.در حالت کوتاه شده به ترتیب stdin و stdout و stderr نامیده می‌شوند. در یک پوسته محاوره‌ای، یا اسکریپت درحال اجرا در ترمینال، ورودی استاندارد طریقی است که bash کاراکترهای تایپ شده توسط شما در صفحه کلیدتان را می‌بیند. خروجی استاندارد جایی است که برنامه اکثر اطلاعات معمولی‌اش را به طوری که کاربر بتواند آن را ببیند، ارسال می‌کند، و خطای استاندارد جایی است که برنامه پیغام‌های خطایش را می‌فرستد. آگاه باشید که برنامه‌های کاربردی رابط گرافیگی هنوز به همین روش عمل می‌کنند، اما رابط گرافیگی واقعی کاربر از طریق این FDها کار نمی‌کند.برنامه‌های رابط گرافیکی هنوز می‌توانند از FDهای استاندارد بخوانند یا در آنها بنویسند، اما به طور معمول چنین نمی‌کنند. معمولاً، آنها تمام ارتباط متقابل با کاربر را از راه آن GUI انجام می‌دهند، که کنترل آن برای BASH را دشوار می‌سازد. در نتیجه، ما در برنامه‌های کاربردی ساده ترمینال خواهیم ماند. برنامه‌هایی، که به آسانی داده‌ها را به ورودی استانداردشان بخورانیم، و داده‌ها را از خروجی استاندارد و خطای استانداردشان بخوانیم.

اجازه بدهید کمی این تعاریف را محسوس‌تر کنیم. در اینجا یک نمایش تجربی از چگونگی کار ورودی استاندارد و خروجی استاندارد می‌آوریم:

    $ read -p "What is your name? " name; echo "Good day, $name.  Would you like some tea?"
    What is your name? lhunath
    Good day, lhunath.  Would you like some tea?

read دستوری است که اطلاعات را از stdin می‌خواند و در یک متغیر ذخیره می‌کند. ما تعیین نموده‌ایم که name آن متغیر باشد. وقتی read یک سطر اطلاعات را از stdin بخواند، خاتمه یافته و echo اجازه می‌یابد یک پیغام نمایش دهد. echo از stdout برای ارسال خروجی‌اش استفاده می‌کند. stdin به دستگاه ورودی پایانه شما که احتمالاً صفحه کلید شما می‌باشد، متصل گردیده است. stdout به دستگاه خروجی پایانه شما متصل گردیده است، که ما فرض می‌کنیم نمایشگر کامپیوتر شما است. در نتیجه، شما می‌توانید نام خود را تایپ نموده و با یک پیغام خوش‌آمد گویی دوستانه در نمایشگر خود به یک فنجان چای دعوت شوید.

پس stderr چیست؟ اجازه دهید نمایش بدهیم:

    $ rm secrets
    rm: cannot remove `secrets': No such file or directory

بدون داشتن فایلی به نام secrets در دایرکتوری جاری خودتان، آن دستور rm با شکست مواجه خواهد شد و یک پیغام خطا در تشریح آنچه اشتباه است، نمایش می‌دهد. چنین پیغام خطاهایی بر حسب قرارداد در stderr نمایش داده می‌شوند. stderr نیز به دستگاه خروجی پایانه شما متصل گردیده است، درست مانند stdout. در نتیجه، پیغام‌های خطا در نمایشگر شما نشان داده می‌شوند درست مانند پیغام‌ها در stdout. به هرحال، این افتراق، جدانگاه داشتن خطاها از پیغام‌های معمول برنامه‌ها را تسهیل می‌کند. بعضی افراد مایل هستند تمام خروجی در stderr را به رنگ قرمز بسته‌بندی کنند، به طوری که بتوانند پیغام‌های خطا را واضح‌تر ببیند. این به طور کلی قابل توصیه نیست، اما یک مثال ساده ازامکانات بسیاری است که این جدایی برای ما فراهم می‌کند. تکنیک رایج دیگر، نوشتن stderr در یک فایل ثبت وقایع(log file) خاص است.


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

        echo "Uh oh.  Something went really bad.." >&2


  • توصیف‌گر فایل: یک شاخص عددی ارجاع به یکی از فرآیندهای فایل باز است. هر دستوری حداقل سه توصیف‌گر اصلی دارد: FD شماره 0، stdin است، FD شماره 1، stdout است و FD شماره 2، stderr می‌باشد.


4. تغییر مسیر

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

همچنین، تغییر مسیر به اشکال مختلف می‌باشد. که عبارتند از تغییر مسیر فایل، دستکاری توصیف‌گرفایل، Heredocها و Herestringها.



  • تغییر مسیر: شیوه تغییر یک توصیف‌گرفایل معین برای خواندن وردی‌اش از جای دیگر یا ارسال خروجی‌اش به جایی دیگر می‌باشد


4.1. تغییر مسیر فایل

تغییر مسیر فایل تغییر یک توصیف‌گر فایل منفرد برای اشاره به یک فایل را شامل می‌گردد. بیایید با یک تغییر مسیر خروجی شروع کنیم:

    $ echo "It was a dark and stormy night.  Too dark to write." > story
    $ cat story
    It was a dark and stormy night.  Too dark to write.

عملگر ‎>‎ یک تغییر مسیر خروجی را شروع می‌کند. تغییر مسیر فقط بر یک فرمان اِعمال می‌گردد(در این حالت، یک دستور echo ). این عملگر به Bash می‌گوید که وقتی آن فرمان را اجرا می‌کند، stdout در عوض جایی که قبلاً اشاره می‌کرد، باید به یک فایل اشاره کند.

در نتیجه، فرمان echo خروجی‌اش را به ترمینال ارسال نمی‌کند، به جای آن، تغییر مسیر ‎> story‎ ، مقصد توصیف‌گر stdout را تغییر می‌دهد به طوری که، حالا به فایلی به نام story اشاره می‌کند. هشیار باشید که این تغییر مسیر قبل از اینکه فرمان echo اجرا بشود، صورت می‌گیرد. به طور پیش‌فرض، BASH اول بررسی نمی‌کند که آیا آن فایل story وجود دارد یا خیر، فقط فایل را باز می‌کند، و اگر از قبل فایلی با آن نام وجود داشته باشد، محتویات قبلی آن از بین می‌رود. اگر فایل وجود نداشته باشد، به صورت یک فایل تهی ایجاد می‌گردد، طوری که آن FDبتواند به آن اشاره کند. این رفتار می‌تواند با گزینه‌های شل تغییر وضعیت یابد(بعداُ می‌بینید).

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

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

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

موقعی که ما برنامه cat را بدون هرگونه شناسه‌ای به کار می‌بریم، به طور آشکاری نمی‌داند که کدام فایل را بخواند. در این وضعیت، cat به جای خواندن از یک فایل، فقط از stdin خواهد خواند( بیشتر همانند read). از آنجاییکه stdin یک فایل عادی نیست، شروع cat بدون هرگونه شناسه، به نظر می‌رسد که کاری انجام نمی‌دهد:

    $ cat

حتی اعلان فرمان را به شما باز نمی‌گرداند! چه اتفاقی رخ می‌دهد؟ cat هنوز در حال خواندن از stdin می‌باشد، که صفحه کلید شماست. حالا هر چیزی که شما تایپ کنید به cat فرستاده خواهد شد. به محض اینکه شما کلید Enter را بزنید، cat همان کاری را خواهد کرد، که به طور معمول انجام می‌دهد: آنچه را خوانده در خروجی استاندارد نمایش خواهد داد، دقیقاً به همان طریقی که فایل story ما را در stdout نمایش داد:

    $ cat
    test?
    test?

حالا چرا ‎test?‎ را دو مرتبه نمایش می‌دهد؟ خوب، همانطور که شما تایپ می‌کنید، ترمینال شما تمام کاراکترهایی را که به stdin می‌فرستید، قبل از ارسال آنها به آنجا، به شما نمایش می‌دهد. نتیجه آن اولین ‎ test?‎ است که شما می‌بینید. به محض اینکه کلید Enter را می‌زنید، cat یک سطر از stdin می‌خواند، وآن را در stdout نمایش می‌دهد، که آن نیز ترمینال شماست، از این‌جهت، سطر دوم : ‎test?‎. می‌توانید با فشردن ‎ Ctrl+D‎ کاراکتر انتهای فایل را به ترمینال ارسال کنید. که باعث می‌گردد، cat گمان کند stdin بسته شده است. خواندن را متوقف خواهد نمود و اعلان فرمان را به شما باز می‌گرداند.

بیایید یک تغییر مسیر ورودی را برای پیوست یک فایل به stdin به کار ببریم، به طوری که stdin دیگر از صفحه کلید ما نخواند، بلکه به جای آن، اکنون از فایل بخواند:

    $ cat < story
    The story of William Tell.

    It was a cold december night.  Too cold to write.

نتیجه این دقیقاً همانند نتیجه ‎ cat story‎ قبلی می‌باشد،غیر از اینکه این دفعه، روش عمل آن کمی متفاوت است. در مثال اول، cat یک FD برای فایل story باز کرد و محتویات فایل را از طریق آن FDخواند. در دومین مثال، cat واقعاً از stdin می‌خواند، دقیقاً مانند موقعی که از صفحه کلید ما خواند. به هرحال، این دفعه، عملگر ‎< story‎، ورودی استاندارد را تغییر داده است، به طوری که منبع اطلاعات، به جای صفحه کلید ما فایل story می‌باشد.

عملگرهای تغییر مسیر میتوانند با تقدم یک عدد همراه گردند. آن عدد توصیف‌گر‌فایلی که تغییر می‌کند را مشخص می‌نماید.

اجازه دهید با چند مثال خلاصه کنیم:

  • command > file: خروجی استاندارد فرمان را به file ارسال می‌کند.

  • command 1> file: خروجی استاندارد فرمان را به file می‌فرستد. چون خروجی استاندارد در FD شماره 1 باز شده، عدد آن رابر عملگر تغییر مسیر مقدم نموده‌ایم. توجه،تأثیر این درست مانند مثال قبل است، زیرا FD شماره 1 پیش‌فرض عملگر ‎>‎ می‌باشد.

  • command < file: موقعی که فرمان از stdin می‌خواند، محتویات file را به کار می‌برد.

  • command 0< file: موقعی که فرمان از stdin می‌خواند، محتویات file را استفاده می‌کند، دقیقاً مانند مثال قبل، چون FD شماره 0 (stdin) پیش‌فرض عملگر ‎ <‎ می‌باشد.

عدد مربوط به FD(توصیف‌گرفایل) stderr شماره 2 می‌باشد. بنابراین بیایید خروجی stderr را به یک فایل ارسال کنیم:

    $ for homedir in /home/*
    > do rm "$homedir/secret"
    > done 2> errors

در این مثال، روی هر دایرکتوری(یا فایل) در شاخه‎ /home حلقه ایجاد می‌کنیم. سپس ما سعی در حذف فایل secret در هر یک از آنها می‌نماییم. برخی دایرکتوری‌های خانگی ممکن است فایل secret نداشته باشند، یا ممکن است اجازه دسترسی برای حذف را نداشته باشیم. در نتیجه، عمل rm ناموفق خواهد بود و پیغام خطا به stderr ارسال خواهد شد.

ممکن است توجه نموده باشید که عملگرتغییر مسیر روی فرمان rm نمی‌باشد، بلکه روی done است. چرا اینطور است؟ خوب، به این ترتیب، تغییر مسیر برای تمام خروجیِ ایجاد شده در حلقه جهت stderr، می‌باشد. به طور تکنیکی، آنچه اتفاق می‌افتد آنست که، Bash فایلی به نام errors را باز می‌کند و stderr را قبل از اینکه حلقه شروع شود، به آن اشاره می‌دهد، سپس وقتی حلقه خاتمه می‌یابد، آن را می‌بندد. هر دستور اجرایی در داخل حلقه(از قبیل rm) از FD باز شده Bash ارث می‌برد.

اجازه بدهید ببینیم نتیجه حلقه ما چه بوده:

    $ cat errors
    rm: cannot remove `/home/axxo/secret': No such file or directory
    rm: cannot remove `/home/lhunath/secret': No such file or directory

دو پیغام خطا در فایل ثبت خطا. دو نفر که فایل secret در دایرکتوری خانگی‌اشان ندارند.

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

    $ for homedir in /home/*
    > do rm "$homedir/secret"
    > done 2> /dev/null

فایل ‎/dev/nullهمیشه خالی است، مسئله‌ای نیست که در آن چه می‌نویسید یا از آن می‌خوانید. همینطور، موقعی که ما پیغام خطاها را در آن می‌نویسیم، آنها ناپدید می‌شوند. فایل ‎/dev/null‎ همچنان مانند قبل خالی می‌ماند. چنین است زیرا یک فایل معمولی نمی‌باشد، یک دستگاه مجازی است. بعضی‌ها ‎/dev/null‎ را bit bucketزیرنویس مترجم 1 می‌نامند.

یک مطلب نهایی هست که باید در باره تغییر مسیر فایل بدانید. جالب است که شما می‌توانید یک فایل ثبت وقایع برای نگهداری پیغام‌های خطایتان ایجاد کنید، اما همانطور که قبلاً اشاره کردم، BASH موقعی که به یک فایل تغییر مسیر داده می‌شود، محتویات موجود آن فایل را ازبین می‌برد. در نتیجه، هر دفعه که ما آن حلقه را برای حذف کردن فایلهای secret اجرا کنیم، فایل ثبت وقایع ماقبل از اینکه دوباره با پیغام‌های جدید پر شود،از سر کوتاه و خالی می‌شود. چه کار باید بکنیم، اگر می‌خواهیم رکوردی از هر پیغام خطای تولید شده در حلقه را حفظ کنیم؟ چه کار کنیم که با هر بار اجرای حلقهزیرنویس مترجم 2 فایل از سر کوتاه نشود؟ چاره کار با دوگانه کردن عملگر تغییر مسیر به دست می‌آید. ‎>‎ می‌شود ‎>>‎. عملگر ‎>>‎ فایل را خالی نمی‌کند بلکه فقط اطلاعات جدید را در انتهای فایل اضافه می‌کند!

    $ for homedir in /home/*
    > do rm "$homedir/secret"
    > done 2>> errors

!Hooray

درضمن، فاصله بین عملگر تغییر مسیر و نام فایل، اختیاری است. بعضی افراد می‌نویسند‎ > file ‎ و برخی می‌نویسند ‎>file‎. هر دو روش صحیح است.


  • تکرار مفید:
    وقتی که یک برنامه به فایل داده‌ای نیاز دارد، و برای خواندن داده از stdin ساخته می‌شود، تغییر مسیر ایده خوبی است. مقدار بسیار زیادی نمونه‌های بد در اینترنت هست، که به شما می‌گویند خروجی cat را به پردازش‌ها لوله‌کشی(بعد می‌خوانید) کنید، اما این چیزی بیش از یک ایده نامساعد نمی‌باشد.
    موقعی که برنامه ای طراحی می‌نمایید که می‌تواند داده‌ها را از منابع مختلفی تغذیه کند، واقعاً اغلب بهترین راه آنست که برنامه شما از stdin بخواند، به این ترتیب، کاربر می‌تواند از تغییر مسیر برای تغذیه برنامه از هر آنچه مایل است، استفاده کند. یک برنامه کاربردی که ورودی استاندارد را از یک مسیر کلی می‌خواند filter نامیده می‌شود.



4.2. دستکاری توصیف‌گر فایل

حال که دانستید چگونه پردازش ورودی و خروجی را با فرستادن به فایلها یا دریافت از آنها اداره کنید، اجازه دهید باز هم کمی آن را جذاب‌تر نماییم.

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

    $ echo "I am a proud sentence." > file

فایلی به نام file ساخته‌ایم، و یک جمله در آن نوشته‌ایم.

برنامه کاربردی به نام grep وجود دارد که به طور مختصر در فصل قبل دیده‌ایم. grep شبیه یک نوار لوله‌ای است: می‌توانید تقریباً در هر پروژه‌ای آن را به کار ببرید(خواه ایده خوبی باشد یا نباشد). اساساً یک الگوی جستجو را به عنوان یک شناسه دریافت می‌کند و می‌تواند چند نام فایل نیز به عنوان شناسه اضافی دریافت نماید. درست مانند cat، برنامه grep نیز اگر نام فایلی را به عنوان شناسه تعیین نکنید از stdin استفاده می‌کند. grep فایلها رامی‌خواند(یا stdin را در صورتکه فایلی مشخص نشده باشد) و الگوی جستجویی که به آن داده‌اید را جستجو می‌کند. اکثر نگارش‌های grep حتی از یک گزینه ‎-r‎ نیز پشتیبانی می‌کنند، که موجب می‌گردد دایرکتوریها را نیز مانند فایلها به عنوان شناسه اضافی قبول کنند، و سپس تمام فایلها و دایرکتوری‌های داخل آن شاخه‌ها را که به آن داده‌اید، جستجو می‌کند. در اینجا مثالی برای آن که grep چگونه می‌تواند عمل کند می‌آوریم:

    $ ls house/
    drawer  closet  dustbin  sofa
    $ grep -r socks house/
    house/sofa:socks

در این مثال تخیلی، دایرکتوری به نام house با چند قطعه اثاثیه منزل در آن به عنوان فایل داریم. اگر ما در جستجوی socks(ساک‌ها)ی خود در هر یک از آن فایلها باشیم، grep را به جستجو در دایرکتوری ‎house/‎ می‌فرستیم. grep هر چیزی در آنجا را جستجو می‌کند، هر فایل را باز می‌کند و محتویات آن را نگاه می‌کند. در مثال ما، grep ساک‌ها( socks) را در فایل ‎house/sofa‎ پیدا می‌کند،احتمالاً پنهان شده زیر یک بالش. مثال واقع‌بینانه‌تری می‌خواهید؟ حتماً:

    $ grep "$HOSTNAME" /etc/*
    /etc/hosts:127.0.0.1       localhost Lyndir

اینجا به grep دستور داده‌ایم هر آنچه ‎$HOSTNAME‎ به آن بسط می‌یابد را در هر فایلی که ‎ /etc/*‎ به آن بسط می‌یابد، جستجو نماید. نام میزبانم را که Lyndir است در فایل ‎/etc/hosts‎ پیدا می‌کند، و سطری از آن فایل را که شامل الگوی جستجو است، به من نشان می‌دهد.

خوب، حال که grep را دریافتید, اجازه بدهید با دستکاری توصیف‌گر‌فایل ادامه دهیم. به خاطر می‌آورید که فایلی به نام file ایجاد کردیم، و یک جمله شامل proud در آن نوشتیم؟ اینک اجازه بدهید grep را برای یافتن جمله proud به کار ببریم:

    $ grep proud *
    file:I am a proud sentence.

صحیح! grep جمله ما را در file یافته است. نتیجه عمل خود را در stdout که در ترمینال ما نمایش داده می‌شود، می‌نویسد. حالا بیایید ببینیم آیا می‌نوانیم با grep یک پیغام خطا نیز ارسال کنیم:

    $ grep proud file 'not a file'
    file:I am a proud sentence.
    grep: not a file: No such file or directory

این دفعه، به grep دستور داده‌ایم رشته proud را درفایلهای 'file' و 'not a file' جستجو کند. file وجود دارد، و جمله در آن هست، بنابراین grep با خوشحالی نتیجه را در stdout می‌نویسد. برای بررسی سراغ فایل بعدی که 'not a file' است، می‌رود. grep نمی‌تواند چنین فایلی را برای خواندن باز کند، به دلیل آنکه وجود ندارد. در نتیجه، grep یک پیغام خطا به stderr که بازهم به ترمینال ما متصل است، ارسال می‌کند.

حال، چطور می‌توان این جمله grep را به طور کامل خاموش کنیم؟ می‌توانیم تمام خروجی که روی ترمینال ظاهر می‌شود را به جای آن به یک فایل بفرستیم، اجازه بدهید آن را proud.log بنامیم:

    # Not quite right....
    $ grep proud file 'not a file' > proud.log 2> proud.log

صحیح به نظر می‌آید؟ ابتدا از ‎ >‎ برای ارسال stdout به فایل proud.log استفاده کرده‌ایم، و سپس از ‎ 2>‎ هم برای ارسال stderr به فایل proud.log. تقریبا درست، اما نه کاملاً. اگر این دستور را اجرا نمایید وسپس به فایل proud.log نگاه کنید، (لااقل در برخی سیستم‌ها) فقط یک پیغام خطا خواهید دید، نه خروجی مربوط به stdout را. در اینجا ما شرایط خیلی نامناسبی ایجاد نموده‌ایم. دو FD ایجاد کرده‌ایم که هر دو به طور مستقل از یکدیگر به یک فایل اشاره می‌کنند. نتایج آن به طور کامل معلوم نیست. بر اساس چگونگی مدیریت توصیف‌گرهای فایل توسط سیستم‌عامل شما، ممکن است بعضاً اطلاعات نوشته شده از طریق یک FD اطلاعات نوشته شده از طریق FD دیگر را پاک کنند.

    $ echo "I am a very proud sentence with a lot of words in it, all for you." > file2
    $ grep proud file2 'not a file' > proud.log 2> proud.log
    $ cat proud.log
    grep: not a file: No such file or directory
    of words in it, all for you.

در اینجا چه اتفاقی رخ داده؟ grep اول فایل file2 را باز کرده، آنچه را به او گفته‌ایم جستجو کند، یافته و سپس جمله very proud ما را در stdout(یعنیFD شماره 1) نوشته است. FD شماره 1 به فایل proud.log اشاره می‌کند، بنابراین اطلاعات در این فایل نوشته شده است. اما، توصیف‌گر‌فایل دیگری هم (FD شماره 2) داریم که به همین فایل و بویژه به ابتدای این فایل اشاره می‌کند. وقتی grep سعی می‌کند فایل 'not a file' را برای خواندن باز کند، نمی‌تواند. پس، یک پیغام خطا در stderr یعنی(FD شماره 2) می‌نویسد، که به ابتدای فایل proud.log اشاره می‌کرد. در نتیجه، دومین عمل نوشتن، اطلاعات اولی را رونویسی می‌کند!

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

    $ grep proud file 'not a file' > proud.log 2>&1

برای درک این مطالب لازم است توجه داشته باشید که: تغییر مسیر فایل از چپ به راست خوانده می‌شود.این ترتیبی است که BASH آنها را پردازش می‌کند. اول، stdout طوری تغییر داده می‌شود که به فایل proud.log اشاره کند. بعد، ما از ترکیب ‎ >&‎ برای دو نسخه‌ای نمودن FD شماره 1 و قرار دادن آن نسخه دوم در FD شماره 2 استفاده کرده‌ایم.

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

مراقب باشید ترتیب آنها را اشتباه نکنید:

    $ grep proud file 'not a file' 2>&1 > proud.log

این مثال stderr را نسبت به جایی که stdout اشاره می‌کند(که ترمینال است) دونسخه‌ای می‌کند، و بعد stdout به فایل proud.log تغییر مسیر داده خواهد شد. در نتیجه پیغام‌های stdout ثبت می‌شوند، اما پیغام خطاها بازهم به ترمینال می‌روند. ای وای.

نکته: برای راحتی، BASH همچنین شکل دیگری از تغییر مسیر را در دسترس شما قرار می‌دهد. عملگر تغییر مسیر ‎&>‎ در حقیقت شکل کوتاه شده‌ای از آنچه ما در اینجا دیدیم، است، تغییر مسیر stdout و stderr هر دو به یک فایل:

    $ grep proud file 'not a file' &> proud.log

این نیز همان ‎proud.log  2>&1‎ می‌باشد، اما قابل حمل به پوسته BourneShell نیست. این تکنیک پیشنهاد شده‌ای نیست، اما اگر ببینید که شخص دیگری از آن در اسکریپت خود استفاده کرده، باید آن را تشخیص بدهید.




4.3. Heredocها و Herestringها

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

    $ grep proud <<END
    > I am a proud sentence.
    > END
    I am a proud sentence.

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

در یک Heredoc، کلمه‌ای برای ایفای نقش نگهبان انتخاب می‌کنیم. هر کلمه‌ای می‌تواند باشد، ما از END در این مثال استفاده کرده‌ایم. کلمه‌ای انتخاب کنید، که درمجموعه داده‌ای شما ظاهر نمی‌شود. تمام سطرهایی که پس از اولین نمونه نگهبان تا رسیدن به دومین نمونه آمده‌اند، برای دستور stdin می‌شوند. دومین نمونه نگهبان، باید خودش یک سطر کامل باشد.

چند مورد متمایز برای Heredocها وجود دارد. به طور معمول، نمی‌توانید آنها را دندانه‌دار کنید، همه فاصله‌هایی که در اینجا برای دندانه‌دار کردن اسکریپت به کار ببرید در stdin ظاهر خواهند شد. رشته پایان دهنده(در مثال ما END) باید در ابتدای سطر باشد.

    echo "Let's test abc:"
    if [[ abc = a* ]]; then
        cat <<END
            abc seems to start with an a!
    END
    fi

چنین نتیجه خواهد داد:

    Let's test abc:
            abc seems to start with an a!

می‌توانید با حذف موقتی توگذاری برای سطرهای Heredocهای خود، از این مطلب اجتناب کنید. به هر حال، موجب بدشکل شدن تورفتگی شکیل و زیبای شما می‌گردد. یک جایگزین وجود دارد. اگر از ‎<<-END‎ به جای ‎<<END به عنوان عملگر Heredoc خود استفاده کنید، BASH همه کاراکترهای tab در ابتدای هر سطر از محتوای Heredoc شما را قبل از ارسال به دستور حذف می‌کند. به این ترتیب بازهم می‌توانید از tabها (اما از فاصله خیر) برای توگذاری محتوای Heredoc خود با بقیه کد استفاده کنید. آن tabها برای فرمانی که Heredoc شما را دریافت می‌کند، ارسال نخواهند شد. همچنین می‌توانید tabها را برای توگذاری رشته نگهبان هم به کار ببرید.

به طور پیش‌فرض، جایگزینی‌های BASH در محتوای Heredoc انجام می‌شود. هر چند، اگر کلمه‌ای که برای جداکردن Heredoc خود به کار می‌برید را نقل‌قولی نمایید، BASH هیچگونه جایگزینی در محتویات انجام نخواهد داد. برای دیدن تفاوت، این مثال را با و بدون کاراکترهای نقل‌قول امتحان کنید:

    $ cat <<'XYZ'
    > My home directory is $HOME
    > XYZ
    My home directory is $HOME

رایج‌ترین کاربرد Heredocها، در ارائه مستندات به کاربر است:

    usage() {
    cat <<EOF
    usage: foobar [-x] [-v] [-z] [file ...]
    A short explanation of the operation goes here.
    It might be a few lines long, but shouldn't be excessive.
    EOF
    }

حالا اجازه بدهید Herestring خیلی مشابه اما فشرده‌تر را آزمایش کنیم:

    $ grep proud <<<"I am a proud sentence"
    I am a proud sentence.

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

    $ grep proud <<<"$USER sits proudly on his throne in $HOSTNAME."
    lhunath sits proudly on his throne in Lyndir.

Herestringها کوتاه‌تر، کمتر مزاحم، و به طور کلی مناسب‌تر از Heredocهای حجیم همتای خود می‌باشند. گرچه آنها قابل حمل به پوسته Bourne نیستند.

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

    $ echo 'Wrap this silly sentence.' | fmt -t -w 20
    Wrap this silly
       sentence.
    $ fmt -t -w 20 <<< 'Wrap this silly sentence.'
    Wrap this silly
       sentence.


  • تکرار مفید:
    heredocهای بلند به طور معمول ایده نامناسبی هستند، زیرا اسکریپت‌ها باید محتوی منطق باشند، نه داده‌ها. اگر اسکریپت به سند حجیمی نیاز دارد، باید آن را در یک فایل جداگانه با اسکریپت همراه کنید. با این وجود Herestringها، اغلب کاملاً سودمند هستند بویژه برای ارسال محتوای یک متغیر(به جای فایلها) به فیلترهایی مانند grep یا sed.



5. لوله‌ها(Pipes)

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

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

    $ ls
    $ mkfifo myfifo; ls
    myfifo
    $ grep bea myfifo &
    [1] 32635
    $ echo "rat
    > cow
    > deer
    > bear
    > snake" > myfifo
    bear

از دستور mkfifo برای ایجاد یک فایل جدید به نام 'myfifo' در دایرکتوری جاری استفاده کردیم. این یک فایل معمولی نیست، بلکه یک FIFO است(که از عبارت First In, First Out اخذ گردیده). فایلهای FIFO فایلهای ویژه‌ای هستند که جهت داده‌های مبتنی بر First In, First Out به کار می‌روند. وقتی از یک FIFO می‌خوانید، فقط داده‌ها را به محض اینکه پردازش دیگری در آن فایل می‌نویسد، دریافت می‌کنید. همینطور یک FIFO هرگز به طورواقعی محتوی هیچ داده‌ای نیست. تا وقتی که پردازشی در آن نمی‌نویسد، هر گونه عملیات خواندن از FIFO در انتظار رسیدن داده‌های معتبر متوقف می‌گردد. همین کار برای نوشتن در FIFO انجام می‌شود -- نوشتن نیز تا وقتی پردازش دیگری در حال خواندن از FIFO است، متوقف می‌شود.

در مثال ما، FIFO با نام myfifo توسط grep خوانده می‌شود. پردازش grep منتظر می‌ماند تا اطلاعات در FIFO قابل دسترس بشوند . اینکه چرا ما عملگر & را به دستور grep پیوست می‌کنیم، برای آنست که آن را در پس‌زمینه قرار دهیم. به این ترتیب، ما می‌توانیم در حالیکه grep در انتظار داده می‌ماند، به تایپ و اجرای دستورات ادامه بدهیم. دستور echo اطلاعات را به FIFO تغذیه می‌کند. به مجرد اینکه این داده قابل دسترس می‌شود، دستور در حال اجرای grep آن را می‌خواند و پردازش می‌کند. نتیجه نمایش داده می‌شود. ما به طور موفقیت‌آمیزی داده را از دستور echo به فرمان grep فرستاده‌ایم.

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

به خاطر این مسائل، ویژگی ‌دیگری در دسترس قرارگرفته است: لوله‌ها. در اصل، لوله فقط stdout یک فرایند را به stdin فرایند دیگر ارتباط می‌دهد، یعنی لوله‌کشی مؤثر داده‌ها از یک پردازش به دیگری. مجموعه کامل دستوراتی را که به این صورت با هم متصل شده‌اند، خط لوله می‌نامند. بیایید مثال فوق را دوباره امتحان کنیم، اما با استفاده از لوله‌ها:

    $ echo "rat
    > cow
    > deer
    > bear
    > snake" | grep bea
    bear

لوله با استفاده از عملگر | بین دو دستور که با هم متصل می‌گردند، ایجاد می‌شود. stdout دستور قبلی به stdin دستور بعدی متصل می‌گردد. در نتیجه، grep می‌تواند خروجی echo را بخواند و نتیجه عملیات خود را که bear است نمایش بدهد.

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

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

    $ message=Test
    $ echo 'Salut, le monde!' | read message
    $ echo "The message is: $message"
    The message is: Test
    $ echo 'Salut, le monde!' | { read message; echo "The message is: $message"; }
    The message is: Salut, le monde!
    $ echo "The message is: $message"
    The message is: Test

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


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




6. عملگرهای گوناگون

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

6.1. جایگزینی پردازش

پسرعموی لوله، عملگر جایگزینی پردازش است، که به دو شکل ظاهر می‌گردد: ‎<()‎ و ‎>()‎. این روش مناسبی برای به کار بردن لوله‌های با نام، بدون لزوم ایجاد فایلهای موقتی می‌باشد. هنگامی که تصور می‌کنید برای انجام مواردی نیاز به فایل موقتی دارید، ممکن است جایگزینی پردازش روش بهتری برای انجام آنها باشد.

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

در اینجا چگونگی انجام آن را در عمل می‌بینیم: موقعیتی را در نظر بگیرید که می‌خواهید تفاوت میان خروجی دو دستور را ببینید. به طور معمول، باید دو خروجی را در دو فایل قرار بدهید و با برنامه diff آنها را مقایسه کنید:

    $ head -n 1 .dictionary > file1
    $ tail -n 1 .dictionary > file2
    $ diff -y file1 file2
    Aachen                                                   | zymurgy
    $ rm file1 file2

می‌توانیم با به کاربردن عملگر جایگزینی پردازش، تمام آن را در یک سطر انجام بدهیم، و نیازی به پاکسازی دستی هم نیست:

    $ diff -y <(head -n 1 .dictionary) <(tail -n 1 .dictionary)
    Aachen                                                   | zymurgy

قسمت ‎ <(..)‎ با فایل FIFO موقتی ایجاد شده توسط bash، تعویض می‌گردد، بنابراین diff در حقیقت، چیزی به این شکل را می‌بیند:

    $ diff -y /dev/fd/63 /dev/fd/62

در اینجا می‌بینیم که وقتی ما از جایگزینی پردازش استفاده می‌کنیم، bash چگونه diff را اجرا می‌کند. دستورهای head و tail را اجرا می‌کند، خروجی‌های آنها را به ترتیب به فایلهای ‎ /dev/fd/63‎ و ‎/dev/fd/62‎ تغییرمسیر می‌دهد. سپس فرمان diff را با قبول کردن آن نام فایلها در جایی که ما عملگرهای جایگزینی پردازش را قرار داده بودیم اجرا می‌کند.

پیاده‌سازی واقعی فایلهای موقتی ازیک سیستم تا سیستم دیگر فرق می‌کند. در حقیقت، با قرار دادن یک دستور echo در ابتدای دستور یک سطری خود، می‌توانید به طور واقعی مشاهده کنید آنچه در بالا گفته شد، به نظر دستور diff خواهد آمد:

    $ echo diff -y <(head -n 1 .dictionary) <(tail -n 1 .dictionary)
    diff -y /dev/fd/63 /dev/fd/62

عملگر ‎>(..)‎ بسیار همانند عملگر ‎ <(..)‎ می‌باشد، اما به جای تغییرمسیر خروجی فرمان به یک فایل، یک فایل را به ورودی فرمان تغییر مسیر می‌دهد. این برای مواردی به کار می‌رود که شما دستوری را اجرا می‌نمایید که در یک فایل می‌نویسد، اما شما می‌خواهید به جای آن در دستور دیگر بنویسد:

    $ tar -cf >(ssh host tar xf -) .


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


  1. مترجم: bit bucket در اصل برای مخزنی موهوم جهت گرفتن بیت‌هایی که با دستورالعمل shift اسمبلی به انتهای یک ثَبّات(register) عقب رانده می‌شوند به کار رفته است (1)

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

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