منبع اصلی نوشتار زیر در این لینک قرار دارد

تغییر مسیر

ادامه یادداشت قبل


راهنمای آموزشی  BashGuide   مؤلف  Lhunath

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 نامیده می‌شود.



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

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


ادامه دارد...