رفتن به محتوا
LoopX

ورودی، خروجی و redirect

توی این درس یاد می‌گیری هر برنامه‌ی لینوکسی سه «لوله» دارد: stdin (ورودی، شماره‌ی ۰)، stdout (خروجی عادی، ۱) و stderr (خطا، ۲). با redirect این لوله‌ها را به فایل یا جای دیگر وصل می‌کنی: > (نوشتن، رونویسی)، >> (افزودن)، 2> (خطا)، 2>&1 (خطا را هم‌جا با خروجی)، < (ورودی از فایل)، و /dev/null برای دور ریختن. با tee هم‌زمان هم می‌بینی و هم ذخیره می‌کنی. تمرین: خروجی موفق و خطای یک دستور را در دو فایل جدا نگه می‌داری.

مسئله: خروجی روی صفحه گم می‌شود

Section titled “مسئله: خروجی روی صفحه گم می‌شود”

دستوری که ۱۰۰۰ خط خروجی دارد یا شبانه از cron اجرا می‌شود، نمی‌تواند خروجی را «روی صفحه» بگذارد؛ کسی آنجا نیست که بخواند. باید خروجی را به فایل بفرستی. و وقتی خطا و خروجی عادی هر دو روی صفحه‌اند، جدا کردنشان سخت است: کدام پیام خطاست؟ redirect همین دو مشکل را حل می‌کند.

تشبیه: لوله‌کشی ساختمان

Section titled “تشبیه: لوله‌کشی ساختمان”

هر برنامه سه شیر دارد: ورودی (آب تمیز می‌آید)، خروجی (فاضلاب عادی می‌رود) و خطا (فاضلاب هشدار جدا می‌رود). به‌طور پیش‌فرض ورودی از صفحه‌کلید و هر دو خروجی به صفحه وصل‌اند. redirect یعنی لوله را جابه‌جا کنی: خروجی به یک مخزن (فایل)، خطا به مخزن دیگر یا به چاه (/dev/null).

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

مثال ۱: دو جریان، یک صفحه

Section titled “مثال ۱: دو جریان، یک صفحه”

دستوری که هم موفق می‌شود هم خطا می‌دهد: ls با دو مسیر، یکی موجود و یکی ناموجود:

Terminal window
ls /etc/hostname /no-such-file
خروجی
/etc/hostname
ls: cannot access '/no-such-file': No such file or directory

دو خط چاپ شد: /etc/hostname (stdout) و پیام خطا (stderr). هر دو روی صفحه آمدند و از هم تشخیص‌پذیر نیستند. با redirect جدایشان می‌کنیم.

مثال ۲: خروجی به فایل، > و >>

Section titled “مثال ۲: خروجی به فایل، > و >>”

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

Terminal window
ls /etc/hostname /no-such-file > out.txt
echo "--- محتوای out.txt:"
cat out.txt
خروجی
ls: cannot access '/no-such-file': No such file or directory
--- محتوای out.txt:
/etc/hostname

فقط stdout در out.txt رفت؛ پیام خطا هنوز روی صفحه آمد (stderr جداست). نکته: > فایل را از اول می‌نویسد:

Terminal window
echo "خط اول" > notes.txt
echo "خط دوم" > notes.txt
cat notes.txt
echo "--- با >> (افزودن):"
echo "خط سوم" >> notes.txt
echo "خط چهارم" >> notes.txt
cat notes.txt
خروجی
خط دوم
--- با >> (افزودن):
خط دوم
خط سوم
خط چهارم

> دوم محتوای قبلی را پاک کرد و فقط «خط دوم» ماند. >> به انتها اضافه می‌کند. برای لاگ و گزارش معمولاً >>؛ برای ساختن فایل تازه >. و > اگر فایل نباشد می‌سازد.

عدد ۲ شماره‌ی stderr است: 2> فقط خطاها را redirect می‌کند:

Terminal window
ls /etc/hostname /no-such-file 2> errors.txt
echo "--- errors.txt:"
cat errors.txt
خروجی
/etc/hostname
--- errors.txt:
ls: cannot access '/no-such-file': No such file or directory

حالا stdout روی صفحه آمد و خطا در فایل رفت؛ دقیقاً برعکس مثال قبل. هر دو را همزمان جدا کن:

Terminal window
ls /etc/hostname /no-such-file > ok.txt 2> err.txt
echo "ok.txt: $(cat ok.txt)"
echo "err.txt: $(cat err.txt)"
خروجی
ok.txt: /etc/hostname
err.txt: ls: cannot access '/no-such-file': No such file or directory

هیچ‌چیز روی صفحه نیامد: خروجی در ok.txt و خطا در err.txt. این همان تمرین درس است. (اگر > را بدون عدد بنویسی، یعنی 1>.)

⚡ بررسی سریع

دستور cmd 2> err.txt چه چیزی را در err.txt می‌ریزد؟

مثال ۴: هر دو در یک فایل، 2>&1 و ترتیب

Section titled “مثال ۴: هر دو در یک فایل، 2>&1 و ترتیب”

برای ریختن هر دو جریان در یک فایل: > فایل 2>&1 (یعنی «stdout به فایل؛ stderr را هم به همان‌جا که stdout می‌رود»):

Terminal window
ls /etc/hostname /no-such-file > all.txt 2>&1
echo "--- all.txt:"
cat all.txt
خروجی
--- all.txt:
ls: cannot access '/no-such-file': No such file or directory
/etc/hostname

میان‌بر bash برای همین: &> فایل. ترتیب مهم است: redirect ها از چپ به راست اعمال می‌شوند:

Terminal window
echo "--- ترتیب درست: > f 2>&1"
ls /etc/hostname /no-such-file > right.txt 2>&1
echo "در فایل: $(wc -l < right.txt) خط"
echo "--- ترتیب اشتباه: 2>&1 > f"
ls /etc/hostname /no-such-file 2>&1 > wrong.txt
echo "در فایل: $(wc -l < wrong.txt) خط (خطا روی صفحه ماند)"
خروجی
--- ترتیب درست: > f 2>&1
در فایل: 2 خط
--- ترتیب اشتباه: 2>&1 > f
ls: cannot access '/no-such-file': No such file or directory
در فایل: 1 خط (خطا روی صفحه ماند)

در ترتیب اشتباه، اول 2>&1 یعنی «stderr را به جایی که الآن stdout هست (صفحه) وصل کن»؛ بعد stdout به فایل می‌رود و stderr روی صفحه می‌ماند. (در خروجی بالا خط خطا بالای «در فایل» دیده شد.) قاعده: اول فایل، بعد 2>&1.

مثال ۵: دور ریختن، /dev/null

Section titled “مثال ۵: دور ریختن، /dev/null”

/dev/null سیاه‌چاله است: هر چه بنویسی گم می‌شود (درس ساختار پوشه‌ها):

Terminal window
echo "--- خطا را ساکت کن:"
ls /etc/hostname /no-such-file 2>/dev/null
echo "--- خروجی عادی را ساکت کن (فقط خطا):"
ls /etc/hostname /no-such-file >/dev/null
echo "--- همه‌چیز ساکت (فقط کد خروج مهم است):"
ls /etc/hostname /no-such-file >/dev/null 2>&1; echo "کد خروج: $?"
خروجی
--- خطا را ساکت کن:
/etc/hostname
--- خروجی عادی را ساکت کن (فقط خطا):
ls: cannot access '/no-such-file': No such file or directory
--- همه‌چیز ساکت (فقط کد خروج مهم است):
کد خروج: 2

الگوی > /dev/null 2>&1 برای «بی‌صدا اجرا کن» در اسکریپت‌ها و cron بسیار رایج است (اکنون می‌دانی یعنی چه). احتیاط: 2>/dev/null خطاها را هم پنهان می‌کند؛ فقط وقتی مطمئنی نیازی به دیدنشان نیست.

< محتوای فایل را به stdin برنامه می‌دهد (برنامه فکر می‌کند از صفحه‌کلید می‌خواند):

Terminal window
printf 'banana\napple\ncherry\n' > fruits.txt
echo "--- wc با نام فایل:"
wc -l fruits.txt
echo "--- wc با ورودی redirect:"
wc -l < fruits.txt
echo "--- sort از stdin:"
sort < fruits.txt
خروجی
--- wc با نام فایل:
3 fruits.txt
--- wc با ورودی redirect:
3
--- sort از stdin:
apple
banana
cherry

فرق دو حالت wc: با اسم فایل، wc خودش فایل را باز می‌کند و اسمش را چاپ می‌کند؛ با < فقط یک جریان می‌بیند و اسمی نمی‌داند (فقط عدد). برای نتیجه‌ی تمیز در اسکریپت wc -l < فایل مفید است. همچنین here-document (<<) یک متن چندخطی را مستقیم به stdin می‌دهد:

Terminal window
cat > config.txt <<'LXEOF'
name=web
port=8080
debug=false
LXEOF
cat config.txt
echo "--- here-string (<<<):"
wc -w <<< "یک رشته‌ی کوتاه برای شمارش"
خروجی
name=web
port=8080
debug=false
--- here-string (<<<):
5

<<'LXEOF' تا خطی که فقط LXEOF باشد را به‌عنوان ورودی می‌خواند (علامت‌های quote دور اسم مانع جایگزینی متغیرها می‌شود). و <<< یک رشته‌ی تک‌خطی را به stdin می‌دهد. این روش نوشتن فایل‌های چندخطی در اسکریپت و درس‌های بعد است.

مثال ۷: همزمان ببین و ذخیره کن، tee

Section titled “مثال ۷: همزمان ببین و ذخیره کن، tee”

tee ورودی‌اش را هم روی صفحه چاپ می‌کند و هم در فایل می‌نویسد (مثل سه‌راهی). معمولاً با | می‌آید (درس بعد):

Terminal window
echo "گزارش اول" | tee log.txt
echo "گزارش دوم" | tee -a log.txt
echo "--- محتوای log.txt:"
cat log.txt
خروجی
گزارش اول
گزارش دوم
--- محتوای log.txt:
گزارش اول
گزارش دوم

بدون -a فایل را رونویسی می‌کند و با -a (append) اضافه می‌کند. کاربرد مهم: نوشتن در فایل‌های root. sudo echo x > /etc/file کار نمی‌کند، چون sudo فقط به echo داده می‌شود و redirect را shell (کاربر عادی) انجام می‌دهد:

Terminal window
sudo echo "سلام" > /etc/lx-test.txt
echo "--- با sudo tee:"
echo "سلام" | sudo tee /etc/lx-test.txt
cat /etc/lx-test.txt
sudo rm /etc/lx-test.txt
خروجی
bash: /etc/lx-test.txt: Permission denied
--- با sudo tee:
سلام
سلام

خطای اول Permission denied از redirect است (shell با اختیار تو آن را انجام می‌دهد)؛ با | sudo tee خود tee با root اجرا می‌شود و می‌تواند بنویسد.

مثال ۸: خالی کردن فایل و کپی استریم‌ها

Section titled “مثال ۸: خالی کردن فایل و کپی استریم‌ها”
Terminal window
echo "مقداری محتوا" > data.txt
ls -l data.txt | awk '{print "قبل:", $5, "بایت"}'
> data.txt
ls -l data.txt | awk '{print "بعد از > data.txt:", $5, "بایت"}'
echo "یک بار دیگر" > data.txt
: > data.txt
ls -l data.txt | awk '{print "بعد از : > data.txt:", $5, "بایت"}'
خروجی
قبل: 24 بایت
بعد از > data.txt: 0 بایت
بعد از : > data.txt: 0 بایت

> فایل به‌تنهایی فایل را به صفر بایت می‌رساند (بدون پاک کردن آن؛ دسترسی‌ها و مالک می‌ماند). : یک دستور «هیچ‌کاری نکن» است و : > فایل همان را در اسکریپت‌ها امن‌تر می‌نویسد. برای لاگ بزرگی که برنامه هنوز باز دارد، همین خالی کردن (نه rm) فضا را آزاد می‌کند.

مثال ۹: یک نگاه به فایل‌های توصیف‌گر

Section titled “مثال ۹: یک نگاه به فایل‌های توصیف‌گر”

هر پروسه لیستی از file descriptor ها دارد: ۰، ۱، ۲ و… که هر کدام به یک فایل/دستگاه/لوله اشاره می‌کند. می‌توانی ببینی redirect چه کرده:

Terminal window
echo "--- بدون redirect (لوله‌ی ترمینال ما):"
ls -l /proc/self/fd | awk 'NR>1 && $9 ~ /^[0-2]$/ {print $9, $10, $11}' | sed 's#/dev/pts/[0-9]*#/dev/pts/N#; s#pipe:\[[0-9]*\]#pipe:[N]#' | head -3
echo "--- با > ورودی و خروجی را به فایل ببر:"
ls -l /proc/self/fd > fds.txt 2> /dev/null < /etc/hostname
awk 'NR>1 && $9 ~ /^[0-2]$/ {print $9, $10, $11}' fds.txt | sed 's#/home/ali/#~/#'
خروجی
--- بدون redirect (لوله‌ی ترمینال ما):
0 -> /dev/null
1 -> pipe:[N]
2 -> pipe:[N]
--- با > ورودی و خروجی را به فایل ببر:
0 -> /etc/hostname
1 -> ~/fds.txt
2 -> /dev/null

در حالت دوم fd شماره‌ی ۰ (stdin) به /etc/hostname، شماره‌ی ۱ (stdout) به fds.txt و شماره‌ی ۲ (stderr) به /dev/null اشاره می‌کند. شل قبل از اجرای ls این اتصال‌ها را درست کرد؛ خود ls فقط از fd ۱ می‌نویسد و نمی‌داند کجا می‌رود.

مثال ۱۰: ترکیب‌های رایج

Section titled “مثال ۱۰: ترکیب‌های رایج”
Terminal window
echo "--- ۱) لاگ‌گیری از اجرای یک کار (هر دو جریان در فایل، پس‌زمینه):"
( sleep 0.2; echo "کار تمام شد"; ls /nope ) > job.log 2>&1 &
wait
cat job.log
echo "--- ۲) فقط خطاها را ببین و به فایل بفرست، خروجی عادی را بیندازد دور:"
ls /etc/hostname /nope > /dev/null 2> only-errors.txt
cat only-errors.txt
echo "--- ۳) stderr را به pipe بفرست (|&):"
ls /etc/hostname /nope |& wc -l
خروجی
--- ۱) لاگ‌گیری از اجرای یک کار (هر دو جریان در فایل، پس‌زمینه):
کار تمام شد
ls: cannot access '/nope': No such file or directory
--- ۲) فقط خطاها را ببین و به فایل بفرست، خروجی عادی را بیندازد دور:
ls: cannot access '/nope': No such file or directory
--- ۳) stderr را به pipe بفرست (|&):
2

|& میان‌بر 2>&1 | است: هم stdout و هم stderr را به دستور بعدی می‌دهد. و در مثال اول & در انتها دستور را در پس‌زمینه می‌برد و wait منتظرش می‌ماند.

پشت پرده: shell قبل از exec، دوباره‌سیم‌کشی می‌کند

Section titled “پشت پرده: shell قبل از exec، دوباره‌سیم‌کشی می‌کند”

وقتی ls > out.txt را می‌زنی، shell (۱) یک فرزند می‌سازد (fork)، (۲) در فرزند، out.txt را باز می‌کند و fd شماره‌ی ۱ را به آن وصل می‌کند (dup2)، (۳) و بعد ls را اجرا می‌کند (exec). ls از fd شماره‌ی ۱ می‌نویسد و اصلاً نمی‌داند فایل است یا صفحه یا لوله. به همین دلیل همه‌ی برنامه‌ها بدون هیچ تغییری با redirect و pipe کار می‌کنند، و برای همین ترتیب redirect ها مهم است: shell آن‌ها را به‌ترتیب چپ به راست اجرا می‌کند.

2>&1 یعنی «fd شماره‌ی ۲ را کپی کن از fd شماره‌ی ۱ در همین لحظه». اگر قبل از آن > فایل آمده باشد، ۱ به فایل اشاره می‌کند و ۲ هم به فایل؛ اگر بعد از آن بیاید، ۲ به مقصد قبلی ۱ (صفحه) اشاره می‌کرد و بعد ۱ جابه‌جا می‌شود. همین است که > f 2>&1 و 2>&1 > f فرق دارند.

یک خطر مهم: > فایل را قبل از اجرای دستور خالی می‌کند. پس sort file.txt > file.txt فایل را خالی می‌کند و بعد sort روی فایل خالی کار می‌کند: داده از بین می‌رود. برای این کار موقت بنویس (sort file.txt > tmp && mv tmp file.txt) یا sort -o file.txt file.txt.

نماد کار
> f stdout به فایل (رونویسی)
>> f stdout به انتهای فایل
2> f stderr به فایل
2>> f stderr به انتهای فایل
> f 2>&1 یا &> f هر دو به فایل
2>&1 stderr را به جای stdout بفرست
< f stdin از فایل
<< 'EOF' here-document (چندخطی)
<<< "متن" here-string
| tee f / tee -a f هم نمایش، هم ذخیره (یا افزودن)
|& stdout و stderr را به pipe بده
/dev/null سیاه‌چاله
شماره نام پیش‌فرض
0 stdin صفحه‌کلید
1 stdout صفحه
2 stderr صفحه

۱) > به‌جای >> و از دست دادن داده

Section titled “۱) > به‌جای >> و از دست دادن داده”

مثال ۲: «خط دوم» محتوای قبلی را پاک کرد. راه‌حل: برای افزودن >>؛ و برای محافظت، در shell تعاملی set -o noclobber را بگذار تا > روی فایل موجود خطا بدهد:

Terminal window
echo "مهم" > precious.txt
set -o noclobber
echo "رونویسی" > precious.txt
echo "--- با >> (افزودن) مشکلی نیست:"
echo "اضافه" >> precious.txt
cat precious.txt
set +o noclobber
خروجی
bash: precious.txt: cannot overwrite existing file
--- با >> (افزودن) مشکلی نیست:
مهم
اضافه

(با >| هم می‌توان آگاهانه رونویسی را اجبار کرد.)

مثال ۴. راه‌حل: اول فایل، بعد 2>&1.

۳) خواندن و نوشتن یک فایل در یک دستور

Section titled “۳) خواندن و نوشتن یک فایل در یک دستور”
Terminal window
printf 'c\na\nb\n' > list.txt
sort list.txt > list.txt
echo "طول list.txt بعد از sort list.txt > list.txt: $(wc -c < list.txt) بایت"
خروجی
طول list.txt بعد از sort list.txt > list.txt: 0 بایت

shell فایل را قبل از sort خالی کرد و همه‌ی داده رفت. راه‌حل: sort list.txt -o list.txt یا فایل موقت.

مثال ۷: redirect با دسترسی تو انجام می‌شود، نه root. راه‌حل: cmd | sudo tee فایل.

۵) پنهان کردن خطا با 2>/dev/null و بعد تعجب از اینکه «کار نکرد»

Section titled “۵) پنهان کردن خطا با 2>/dev/null و بعد تعجب از اینکه «کار نکرد»”
Terminal window
cp /no/such/file /tmp/x 2>/dev/null; echo "کد خروج: $?"
خروجی
کد خروج: 1

دستور بی‌صدا شکست خورد ولی هیچ پیامی ندیدی؛ فقط کد خروج غیرصفر نشانه بود. راه‌حل: خطا را در یک فایل لاگ بریز (2>> errors.log) نه جای سیاه‌چاله، و کد خروج را چک کن.

✎ تمرینآسان

خروجی date را در فایل today.txt بنویس، بعد خروجی whoami را به انتهای همان فایل اضافه کن و محتوا را نشان بده.

دیدن جواب
Terminal window
date +%Y-%m-%d > today.txt
whoami >> today.txt
cat today.txt
خروجی
2026-10-03
ali
✎ تمرینمتوسط

تمرین اصلی: خروجی موفق و خطای یک دستور را در دو فایل جدا ذخیره کن. دستور ls /etc/hostname /etc/os-release /no-file را طوری اجرا کن که خروجی در ok.txt و خطا در err.txt برود، بعد تعداد خط هر فایل را بنویس.

دیدن جواب
Terminal window
ls /etc/hostname /etc/os-release /no-file > ok.txt 2> err.txt
echo "ok.txt ($(wc -l < ok.txt) خط):"; cat ok.txt
echo "err.txt ($(wc -l < err.txt) خط):"; cat err.txt
خروجی
ok.txt (2 خط):
/etc/hostname
/etc/os-release
err.txt (1 خط):
ls: cannot access '/no-file': No such file or directory
✎ تمرینسخت

یک تابع run-logged بنویس که یک دستور بگیرد و اجرا کند، خروجی عادی را در run.out، خطاها را در run.err ذخیره کند، همزمان هر دو را روی صفحه با پیشوند [OUT] یا [ERR] نشان بدهد (راهنما: process substitution یا tee)، و در پایان کد خروج را چاپ کند. روی ls /etc/hostname /nope امتحان کن.

دیدن جواب
Terminal window
run-logged() {
"$@" > >(tee run.out | sed 's/^/[OUT] /') 2> >(tee run.err | sed 's/^/[ERR] /' >&2)
local rc=$?
sleep 0.3
echo "کد خروج: $rc"
}
run-logged ls /etc/hostname /nope 2>&1
echo "--- run.out:"; cat run.out
echo "--- run.err:"; cat run.err
خروجی
[ERR] ls: cannot access '/nope': No such file or directory
[OUT] /etc/hostname
کد خروج: 2
--- run.out:
/etc/hostname
--- run.err:
ls: cannot access '/nope': No such file or directory

>(...) (process substitution) یک «فایل» می‌سازد که ورودی‌اش به یک دستور می‌رود؛ اینجا tee هم ذخیره می‌کند و هم sed پیشوند می‌گذارد.

؟ آزمونک
  1. شماره‌ی stdout و stderr؟

  2. تفاوت > و >>؟

  3. کدام دستور هر دو جریان را در یک فایل می‌ریزد؟

  4. cmd >/dev/null 2>&1 چه می‌کند؟

  5. چرا sudo echo x > /etc/file کار نمی‌کند؟

  6. چرا sort f > f خطرناک است؟

  • هر برنامه سه جریان دارد: stdin (0)، stdout (1)، stderr (2)؛ shell قبل از اجرا آن‌ها را وصل می‌کند.
  • > رونویسی، >> افزودن، 2> خطا، &> یا > f 2>&1 هر دو (اول فایل، بعد 2>&1)، < ورودی از فایل، << here-document.
  • /dev/null سیاه‌چاله؛ >/dev/null 2>&1 یعنی ساکت.
  • tee هم نشان می‌دهد هم ذخیره می‌کند؛ | sudo tee برای نوشتن در فایل‌های root.
  • > فایل را قبل از اجرای دستور خالی می‌کند؛ هرگز cmd f > f ننویس. set -o noclobber محافظ است.
  • خطاها را جدا (2> errors.log) نگه دار تا پیدا کردنشان آسان باشد؛ بی‌دلیل 2>/dev/null نگذار.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
cmd > out.txtخروجی به فایل (رونویسی)
cmd >> out.txtخروجی به انتهای فایل
cmd 2> err.txtفقط خطا به فایل
cmd > out.txt 2> err.txtخروجی و خطا در دو فایل جدا
cmd > all.txt 2>&1 (یا &> all.txt)هر دو در یک فایل
cmd > /dev/null 2>&1اجرای بی‌صدا
cmd < in.txtورودی از فایل
cmd | tee log.txtهم نمایش، هم ذخیره
cmd | tee -a log.txtافزودن به انتهای فایل
echo x | sudo tee /etc/fنوشتن در فایل root
> file (یا : > file)خالی کردن فایل
set -o noclobberجلوگیری از رونویسی تصادفی با >