فصل ۶ – تغییر مسیر ورودی و خروجی (Redirection)


مقدمه

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

در این فصل به مباحث زیر می‌پردازیم:


ورودی و خروجی استاندارد

هر دستور حداقل با سه «جریان» (stream) سروکار دارد:

  1. ورودی استاندارد (Standard Input یا stdin): جریان داده‌ای که دستور از آن می‌خواند. به طور پیش‌فرض صفحه‌کلید است.
  2. خروجی استاندارد (Standard Output یا stdout): جریان داده‌ای که دستور نتایج معمول خود را در آن می‌نویسد. پیش‌فرض نمایشگر (ترمینال) است.
  3. خروجی خطای استاندارد (Standard Error یا stderr): جریان جداگانه‌ای برای پیام‌های خطا و هشدارها. آن هم پیش‌فرض روی نمایشگر است.

در پشت صحنه، سیستم‌عامل برای این جریان‌ها شماره‌هایی در نظر می‌گیرد که «توصیف‌گر فایل» (file descriptor) نام دارند:

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


تغییر مسیر خروجی استاندارد

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

ls > ls-output.txt

این دستور خروجی ls را در فایل ls-output.txt ذخیره می‌کند. اگر فایل از قبل وجود داشته باشد جایگزین می‌شود.
در صورت نیاز به افزودن خروجی جدید به انتهای فایل بدون حذف محتوا از >> کمک می‌گیریم:

ls >> ls-output.txt

گاهی می‌خواهیم خروجی را دور بریزیم. دستگاه ویژه‌ای به نام /dev/null وجود دارد که هر داده‌ای به آن ارسال شود ناپدید می‌گردد:

ls -l /usr/bin > /dev/null

این روش برای ساکت‌کردن برنامه‌ها هنگام اجرای اسکریپت‌ها مفید است.

تغییر مسیر پیام‌های خطا

اگر دستوری هم خروجی معمول و هم پیام خطا تولید کند، می‌توانیم آن‌ها را جداگانه مدیریت کنیم. برای ارسال stderr به فایل از 2> استفاده می‌کنیم:

ls -l /not-here 2> ls-errors.txt

برای افزودن پیام‌های خطا به انتهای فایل موجود:

ls -l /not-here 2>> ls-errors.txt

ارسال هم‌زمان stdout و stderr به یک فایل نیز ممکن است. یکی از روش‌های رایج استفاده از &> است:

ls -l /usr/bin &> ls-full.log

یا می‌توانیم stderr را به stdout هدایت کنیم و سپس آن‌ها را با هم تغییر مسیر دهیم:

ls -l /usr/bin > ls-full.log 2>&1

در این مثال ابتدا stdout به فایل می‌رود و سپس stderr به همان مقصد هدایت می‌شود.

محافظت در برابر رونویسی ناخواسته

گاهی فراموش می‌کنیم فایلی وجود دارد و با > آن را از بین می‌بریم. برای جلوگیری از این اتفاق، شل bash گزینه‌ای به نام noclobber دارد:

set -o noclobber
ls > ls-output.txt # اگر فایل وجود داشته باشد خطا می‌دهد.

برای اجازه‌ی رونویسی موقتی بدون غیرفعال کردن noclobber می‌توان از >| استفاده کرد:

ls >| ls-output.txt

برای بازگشت به رفتار پیش‌فرض و اجازه‌ی رونویسی کافی است noclobber را خاموش کنیم:

set +o noclobber

تغییر مسیر ورودی استاندارد

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

sort < unsorted-list.txt

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

استفاده‌های کاربردی از cat

دستور cat (مخفف concatenate) برای نمایش محتویات فایل‌ها و اتصال آن‌ها استفاده می‌شود:

cat ls-output.txt

با ترکیب cat و تغییر مسیر خروجی می‌توانیم چند فایل را در یکی ادغام کنیم:

cat part1.txt part2.txt > whole.txt

علاوه بر این می‌توانیم با اجرای cat > note.txt فایلی تازه بسازیم و متن دلخواه را تایپ کنیم؛ فشردن کلیدهای Ctrl+D (در لینوکس) پایان ورودی را اعلام می‌کند. برای افزودن متن جدید به همان فایل نیز می‌توانیم از cat >> note.txt استفاده کنیم.

یا خروجی یک دستور را به عنوان ورودی cat بدهیم و نتیجه را ثبت کنیم:

cat <<MARKER > memo.txt
لطفاً گزارش را تا سه‌شنبه ارسال کنید.
با تشکر
MARKER

در مثال بالا، از «تغییر مسیر درون‌خطی» (here document) استفاده کردیم؛ هر چیزی بین MARKER اول و دوم نوشته شود، به عنوان ورودی cat در نظر گرفته می‌شود.


لوله‌کشی داده‌ها با |

علاوه بر فایل‌ها، می‌توان خروجی یک دستور را مستقیماً به ورودی دستور بعدی وصل کرد. عملگر | یا «pipe» این کار را انجام می‌دهد:

ls -l /usr/bin | less

در اینجا، ls خروجی مفصل خود را تولید می‌کند و less همان خروجی را صفحه‌به‌صفحه نمایش می‌دهد. زنجیره‌های طولانی‌تری هم می‌توانیم بسازیم:

cat /etc/passwd | sort | less

هر فرمانی که داده را از ورودی استاندارد می‌خواند و روی خروجی استاندارد می‌نویسد، در لوله‌ها قابل استفاده است.


فیلترهای پرکاربرد

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

با ترکیب این فیلترها می‌توانیم فرآیندهای پردازشی پیچیده‌ای بسازیم بدون آن‌که لازم باشد اسکریپت‌های طولانی بنویسیم.

مثال:

ls /usr/bin | sort | uniq | head

نگه داشتن و مشاهده‌ی هم‌زمان خروجی با tee

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

ls -l /usr/bin | tee ls.log | less

در این مثال، خروجی مفصل ls برای مشاهده به less می‌رود و tee همان خروجی را در ls.log ذخیره می‌کند. با گزینه‌ی -a می‌توانیم داده‌ها را به انتهای فایل بیفزاییم:

command | tee -a combined.log

جمع‌بندی

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