مقدمه

می‌رسیم به یکی از مهمترین ویژگی‌های لینوکس یعنی Redirection یا به بیان دقیق‌تر، I/O Redirection که به معنای تغییر رفتار پیش‌فرض سیستم‌های لینوکسی در کار با ورودی‌ها و خروجی‌هاست. بر اساس این رفتار پیش‌فرض، خروجی کامندها در صفحه‌ی ترمینال نمایش داده می‌شود و ورودی‌ها از کیبورد دریافت می‌شوند. اما می‌توانیم ترتیبی بدهیم که خروجی کامندها به جای نمایش در صفحه‌ی ترمینال، در یک فایل متنی ذخیره شود و یا ورودی کامندها به جای کیبورد، از یک فایل دریافت شود. علاوه بر این، در مورد مفهوم کلیدی Piping نیز صحبت خواهیم کرد که این امکان را فراهم می‌کند که خروجی یک کامند را به عنوان ورودی به یک کامند دیگر بدهیم. در این راستا با کامندهای مهمی مانند cat، grep، head و tail و چد کامند دیگر نیز آشنا خواهیم شد.

فایل‌های stdout، stdin و stderr

یک بار دیگر فلسفه و تم حاکم بر لینوکس را یادآوری می‌کنم: در لینوکس، هر چیزی یک فایل است. اما آیا رفتار پیش‌فرضی که لینوکس با اعمال I/O دارد، با این اصل سازگار است؟

یک کامند می‌تواند با تولید خروجی مورد نظرش همراه باشد و یا اینکه خطا تولید کند. کامندهایی مانند ls نتایج خود را برای یک فایل خاص با نام standard output که به‌اختصار stdout نامیده می‌شود، ارسال می‌کنند و پیغام‌های خطا و گزارش وضعیتشان را به فایل خاصی به نام standard error یا stderr ارسال می‌کنند. از آنجایی که هر دوی این فایل‌ها در حالت پیش‌فرض به صفحه‌ی ترمینال لینک هستند، این خروجی‌ها در ترمینال نمایش داده می‌شود. در واقع، علت اینکه از لفظ خاص برای این دو فایل استفاده کردیم، این است که اینها فایل‌های ذخیره شده در دیسک نیستند، بلکه به صفحه‌ی ترمینال لینک هستند.

به علاوه، اکثر کامندها ورودی مورد نیاز خود را از فایلی به نام standard input یا stdin دریافت می‌کنند که در حالت پیش‌فرض به کیبورد لینک است.

I/O Redirection به ما امکان می‌دهد که این رفتارهای پیش‌فرض را تغییر دهیم و خروجی را به جایی غیر از ترمینال هدایت کنیم و ورودی را از منبعی غیر از کیبورد دریافت کنیم. این کار به منزله‌ی ریدایرکت فایل‌های stdout، stderr و stdin است که نحوه‌ی انجام آن را در ادامه خواهیم دید.

ریدایرکت stdout

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

اما ریدایرکت stdout به این معناست که ترتیبی دهیم تا این فایل به جای ترمینال، به یک فایل متنیِ ذخیره شده روی دیسک لینک شود و نتیجتاً خروجی کامندها (به جای نمایش در ترمینال) در آن فایل متنی ذخیره شود. این کار با استفاده از عملگر ریدایرکتِ stdout که با > نمایش داده می‌شود، قابل انجام است. در مثال زیر، از این عملگر استفاده کرده‌ایم تا خروجی کامند ls به‌جای نمایش در ترمینال، در فایلی با نام output.txt ذخیره شود.

$ ls -l > output.txt

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

$ less output.txt

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

$ ls -e > output.txt
ls: invalid option -- 'e'
Try 'ls --help' for more information.
          

در این کامند از یک آپشن -e برای کامند ls استفاده شده اما ls فاقد چنین آپشنی است و بنابراین، یک پیغام خطا تولید شده و در ترمینال نمایش داده شده است. باز هم تکرار می‌کنم که نباید انتظار داشته باشید که این پیغام خطا در فایل output.txt ذخیره شود؛ چون عملگر > فایل stdout را ریدایرکت می‌کند اما پیغام‌های خطا به فایل stderr ارسال می‌شوند.

اما اگر الان یک بار دیگر محتوای فایل output.txt را با کامند less نمایش دهید، خواهید دید که این فایل خالی شده و محتوایی ندارد. چون همانطور که در بالا اشاره کردیم، وقتی ما خروجی را با استفاده از عملگر > ریدایرکت می‌کنیم، فایل مقصد همیشه بازنویسی می‌شود. یعنی در اینجا فایل output.txt خالی می‌شود تا محتوای جدید را دریافت کند اما وقوع خطا باعث می‌شود که محتوایی برای این فایل ارسال نشود و در عوض، پیغام خطای تولید شده به stderr ارسال شود.

به نظرتان نتیجه‌ی اجرای کامند > text.txt چیست؟ گفتیم که عملگر > همیشه فایل مقصد را بازنویسی می‌کند. پس، در اینجا محتوای فایل test.txt حذف می‌شود اما چون قبل از عملگر > چیزی وجود ندارد، بازنویسی انجام نخواهد شد و نتیجتاً فایل خالی می‌ماند. پس، این کامند باعث می‌شود که فایل test.txt در صورت وجود، خالی شود و اگر موجود نباشد، ساخته شود. از این ترفند می‌توانیم برای ساخت یک فایل خالی یا حذف محتوای یک فایل استفاده کنیم.

ریدایرکت بدون بازنویسی

عملگر ریدایرکتِ stdout یک ورژن دیگر هم دارد که با >> نمایش داده می‌شود. این عملگر، به جای بازنویسی فایل مقصد،خروجی کامند را به آن اضافه (append) می‌کند.

$ date > output.txt 
$ pwd >> output.txt 
$ less output.txt
Wed Sep 25 12:03:09 +0330 2024
/home/hagrid
          

در اینجا کامند اول باعث می‌شود که فایل output.txt با خروجی کامند date بازنویسی شود. در واقع، اگر فایل output.txt وجود نداشته باشد، ساخته می‌شود و خروجی کامند date در آن ذخیره می‌شود و اگر این فایل موجود باشد، با خروجی کامند date بازنویسی می‌شود. اما در کامند دوم از عملگر >> استفاده شده که باعث می‌شود خروجی کامند pwd به فایل output.txt اضافه شود.

ریدایرکت stderr

هر یک از فایل‌های stdout، stdin و stderr دارای یک عدد موسوم به file descriptor هستند. این عدد برای stdout برابر با صفر، برای stdin برابر با 1 و برای stderr برابر با 2 است. برای ریدایرکت stderr باید از عدد متناظر آن یعنی 2 قبل از عملگر > استفاده کنیم.

$ cd /hello/bye 2> error.txt

در اینجا از مسیری برای کامند cd استفاده کرده‌ایم که وجود خارجی ندارد و بنابراین، خروجی این کامند، یک پیغام خطاست. در غیاب عملگر ریدایرکت stderr، این پیغام در ترمینال نمایش داده می‌شود اما چون در اینجا با استفاده از عملگر 2> فایل stderr را ریدایرکت کرده‌ایم، پیغام خطا در فایلی به نام error.txt ذخیره خواهد شد.

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

ریدایرکت stdout و stderr به یک فایل

گاهی اوقات می‌خواهیم خروجی یک کامند، چه از نوع مورد انتظار باشد و چه یک پیغام خطا، در هر دو صورت در یک فایل یکسان ذخیره شود. در این صورت، باید هر دو فایل stdout و stderr را به یک فایل یکسان ریدایرکت کنیم. برای این کار، می‌توانیم از عملگر &> استفاده کنیم. مثال زیر را ببینید.

$ ls -l /usr/bin &> output.txt

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

جلوگیری از نمایش و ذخیره خروجی

ممکن است گاهی بخواهیم ترتیبی دهیم که پیغام‌های خطا نه در ترمینال نمایش داده شوند و نه در فایلی ذخیره شوند. در لینوکس، یک فایل خاص با نام /dev/null وجود دارد که دقیقاً همین کار را انجام می‌دهد؛ یعنی خروجی کامند را نیست و نابود می‌کند. کافیست کامند مورد نظر را به این فایل ریدایرکت کنیم.

$ ls -l /hello/world 2> /dev/null

در اینجا کامند ls آرگومان نامعتبری (یک دایرکتوری ناموجود) دریافت کرده و بنابراین، با خطا همراه است. اما چون فایل stderr را به /dev/null ریدایرکت کرده‌ایم، پیغام خطا نمایش داده نمی‌شود و ذخیره هم نمی‌شود. پس، با اجرای این کامند، هیچ اتفاقی نمی‌افتد.

ریدایرکت stdin

قبل از اینکه در مورد ریدایرکت stdin صحبت کنیم، ابتدا یک کامند جدید با نام cat را معرفی می‌کنیم. این کامند که نامش از روی واژه‌ی concatenate به معنای الحاق و ادغام، گرفته شده، یک یا چند فایل را می‌خواند و آنها را در stdout کپی می‌کند. اگر فقط یک فایل را به عنوان آرگومان این متد تعیین کنیم، محتوای این فایل در ترمینال نمایش داده می‌شود.

$ cat /etc/passwd

اگر این کامند را اجرا کنید، خواهید دید که محتویات فایل /etc/passwd در ترمینال نمایش داده می‌شود. پس، تا اینجا کامند cat مثل کامند less محتوای فایل را در ترمینال نمایش می‌دهد اما با این تفاوت که cat قادر به صفحه‌بندی محتوا نیست. اما در عوض، cat می‌تواند بیش از یک فایل را به عنوان آرگومان دریافت کند، آنها را با هم ادغام کند و نتیجه را در ترمینال نمایش دهد. به مثال زیر توجه کنید.

$ echo "Hello" > greeting1
$ echo "How are you?" > greeting2
$ cat greeting1 greeting2
Hello 
How are you? 
$ cat greeting1 greeting2 > greeting
$ cat greeting 
Hello
How are you?
          

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

$ cat
The quick brown fox jumped over the lazy dog. 
The quick brown fox jumped over the lazy dog.
Hello
Hello
          

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

$ cat > lazy_dog.txt
The quick brown fox jumped over the lazy dog.
          

حالا می‌توانیم به بحث ریدایرکت stdin بپردازیم که با استفاده از عملگر < قابل انجام است. این عملگر stdin را ریدایرکت می‌کند و بنابراین، می‌توان با استفاده از آن ترتیبی داد که یک کامند، ورودی خود را به جای کیبورد از یک فایل دریافت کند.

$ cat < lazy_dog.txt 
The quick brown fox jumped over the lazy dog
          

در اینجا کامند cat ورودی خود را از فایل lazy_dog.txt دریافت کرده و آن را نمایش داده است. البته در مورد کامند cat دیدیم که بدون استفاده از عملگر < هم می‌توان این کار را انجام داد اما بعداً کامندهایی خواهیم دید که استفاده‌های بهتری از این عملگر می‌کنند.

نقش عملگر Pipe

Piping یک ویژگی شِل است که به ما امکان می‌دهد خروجی یک دستور را به عنوان ورودی به یک دستور دیگر بدهیم. در واقع، پایپینگ نوعی ریدایرکت را انجام می‌دهد. عملگر پایپ ( | ) برای پیاده‌سازی این نوع ریدایرکت کاربرد دارد.

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

$ ls -l /usr/bin | less

اگر این کامند را اجرا کنید، خواهید دید که خروجی کامند ls توسط less نمایش داده می‌شود. در واقع، less خروجی هر کامندی را که نتایجش را برای stdout می‌فرستد، به عنوان ورودی دریافت می‌کند. این یک ویژگی بسیار مفید و به‌دردبخور است؛ چون به ما امکان می‌دهد که خروجی هر کامندی را به صورت صفحه‌بندی‌شده ببینیم.

فیلترها

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

یکی از کامندهایی که زیاد به عنوان فیلتر به کار می‌رود، کامند sort است که همانطور که از نامش پیداست، ورودی خود را مرتب می‌کند. در مثال زیر از این کامند استفاده شده است.

$ cat > asian_countries
Iran
Japan
China
Kuwait
Turkey
South Korea 
$ cat > european_countries 
England
Gernaby
Italy
Turkey
Spain
$ cat asian_countries european_countries | sort | less
China
England
Germany
Iran
Italy
Japan
Kuwait
South Korea
Spain
Turkey
Turkey
          

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

یک فیلتر دیگر که معمولاً به همراه کامند sort به کار می‌رود، کامند uniq است که خطوط تکراری در ورودی خود را حذف می‌کند.

$ cat asian_countries european_ciuntries | sort | uniq | less

در اینجا کامند uniq به عنوان یک فیلتر به زنجیره‌ی کامندها اضافه شده و در لیست مرتب‌شده‌ای که از کامند sort تحویل می‌گیرد، خطوط تکراری را حذف می‌کند و نتیجه را به کامند less می‌دهد. خروجی به صورت زیر خواهد بود.

China
England
Germany
Iran
Italy
Japan
Kuwait
South Korea
Spain
Turkey
          

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

$ cat asian_countries european_ciuntries | sort | uniq -d | less

دریافت اطلاعات فایل‌ها

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

  • کامند wc برای دریافت تعداد خطوط، کلمات و بایت‌ها در یک فایل.
  • کامند grep برای پیدا کردن عباراتِ دارای یک الگوی مشخص در یک فایل.
  • کامند head برای نمایش خطوط ابتدایی یک فایل.
  • کامند tail برای نمایش خطوط انتهایی یک فایل.

کامند wc

کامند wc تعداد خطوط، کلمات و بایت‌های یک فایل را محاسبه کرده و نمایش می‌دهد. در مثال زیر از این کامند برای فایل /etc/passwd استفاده شده است.

$ wc /etc/passwd
28   38 1473 /etc/passwd
          

که نشان می‌دهد در این فایل 28 حط و 38 کلمه و 1473 بایت وجود دارد. در فایل‌هایی که فقط از کاراکترهای ASCII تشکیل شده باشند، تعداد بایت‌‌ها نشان دهنده‌ی تعداد کاراکترهاست.

اگر فقط تعداد خطوط یک فایل را بخواهیم، می‌توانیم از آپشن -l برای کامند wc استفاده کنیم. آپشن‌های -w و -c هم به‌ترتیب برای نمایش تعداد کلمات و تعداد کاراکترها کاربرد دارند.

$ wc -l /etc/passwd
28 /etc/passwd
          

کامند grep

کامند grep برای پیدا کردن خطوطی از یک فایل که شامل عبارتی با یک الگوی مشخص هستند، کاربرد دارد. برای مثال، فرض کنید بخواهیم از لیست برنامه‌هایی که در دایرکتوری‌های /bin و /usr/bin قرار دارند، هر کدام را که شامل عبارت zip هستند، چاپ کنیم. این کار به صورت زیر قابل انجام است.

$ ls /bin /usr/bin | sort | uniq | grep zip

از آنجایی که ممکن است برنامه‌ای داشته باشیم که هم در دایرکتوری /bin و هم در دایرکتوری /usr/bin باشد، از فیلترهای sort و uniq برای مرتب‌سازی و حذف نام برنامه‌های تکراری استفاده کرده‌ایم.

برای کامند grep دو آپشن مفید داریم:

  • آپشن -i که باعث می‌شود هنگام جستجوی فایل، بزرگی و کوچکی حروف در نظر گرفته نشود؛ یعنی برای مثال، test و Test دو عبارت یکسان در نظر گرفته شوند. در غیاب این آپشن، جستجو به صورت حساس به حروف (case sensitive) انجام می‌شود.
  • آپشن -v که نتیجه‌ی جستجو را معکوس می‌کند؛ یعنی باعث می‌شود خطوطی چاپ شوند که با الگوی مورد نظر مطابقت ندارند.

کامندهای head و tail

گاهی اوقات فقط به چند خط ابتدایی یا چند خط انتهایی یک فایل نیاز داریم. در این مواقع، می‌توانیم از کامندهای head و tail استفاده کنیم که اولی، 10 خط ابتدایی و دومی، 10 خط انتهایی فایل را نمایش می‌دهند. البته با استفاده از آپشن -n می‌توانیم تعداد خطوط را از عدد پیش‌فرض 10 به هر عدد دیگر تغییر دهیم.

$ head -n 3 asian_countries
Iran
Japan
China
$ tail -n 2 asian_countries
Turkey
South Korea
          

حالا اجازه دهید یک کاربرد جالب از این کامندها را در عمل ببینیم. فرض کنید می خواهیم مدل CPU کامپیوترمان را پیدا کنیم. در اکثر توزیع‌های لینوکسی، کامندی با نام lscpu داریم که مشخصات کاملی از CPU را ارائه می‌دهد. اگر این کامند را اجرا کنید، خواهید دید که در خط هشتمِ خروجی، که با عبارت Model name شروع می‌شود، اطلاعات مورد نظر ما ارائه شده است. حالا می‌خواهیم با ترکیب چند کامند با تکنیک پایپینگ، این خط را در خروجی نمایش دهیم.

$ lscpu | head -n 8 | tail -n 1 | less

چیزی شبیه خروجی زیر توسط less نمایش داده خواهد شد.

Model name:                         Intel(R) Core(TM) i7-4700HQ CPU @ 2.40GHz