مدیریت ورودی و خروجی یکی از قدرتمندترین ویژگیهای Bash است. با استفاده از بازنویسی (redirection)، لولهها (pipes)، اینجا-اسناد (here documents) و اینجا-رشتهها (here strings) میتوانید جریان داده را بین فایلها، دستورات و متغیرها بهصورت کاملاً انعطافپذیر هدایت کنید. درک این مکانیزمها برای نوشتن اسکریپتهای کارآمد و حرفهای ضروری است.
مفاهیم پایه: جریانهای استاندارد (Standard Streams)
هر فرآیند در سیستمعاملهای شبهیونیکس با سه جریان داده باز شروع به کار میکند:
| شماره (File Descriptor) | نام | توضیح |
|---|---|---|
0 | stdin (ورودی استاندارد) | ورودی دادهها به برنامه (معمولاً صفحهکلید) |
1 | stdout (خروجی استاندارد) | خروجی عادی برنامه (معمولاً ترمینال) |
2 | stderr (خطای استاندارد) | خروجی پیامهای خطا (معمولاً ترمینال) |
Bash به شما اجازه میدهد این جریانها را به فایلها، دستگاهها، یا دیگر دستورات هدایت کنید.
بازنویسی (Redirection)
بازنویسی یعنی تغییر مسیر یک جریان داده از مقصد پیشفرض به جای دیگری. عملگرهای اصلی بازنویسی به این شرح هستند:
۱. بازنویسی خروجی استاندارد (> و >>)
-
>: خروجی را در یک فایل نوشته (overwrite) میکند. اگر فایل وجود نداشته باشد، ایجاد میشود. -
>>: خروجی را به انتهای فایل اضافه میکند (append).
# نوشتن خروجی در فایل (پاک کردن محتوای قبلی) echo "سلام دنیا" > output.txt # اضافه کردن به انتهای فایل echo "خط دوم" >> output.txt # خروجی یک دستور را در فایل ذخیره کنید ls -la > listing.txt
۲. بازنویسی ورودی استاندارد (<)
ورودی یک دستور را از یک فایل تأمین میکند، نه از صفحهکلید.
# ارسال محتوای فایل به ورودی دستور wc (شمارش کلمات) wc -w < myfile.txt # خواندن خط اول فایل با دستور read read -r first_line < data.txt
۳. بازنویسی خطاهای استاندارد (2> و 2>>)
برای هدایت پیامهای خطا بهطور جداگانه از خروجی عادی:
# ارسال خطاها به یک فایل جداگانه command_that_may_fail 2> errors.log # اضافه کردن خطاها به فایل خطا command 2>> errors.log
۴. ترکیب خروجی و خطا در یک فایل
گاهی میخواهید هر دو جریان stdout و stderr را به یک مکان بفرستید:
# روش اول: استفاده از 2>&1 (معنی: stderr را به همان مقصدی بفرست که stdout میرود) command > output.log 2>&1 # روش دوم: استفاده از &> (مخفف روش اول در Bash) command &> output.log # روش سوم: اضافه کردن هر دو به انتهای فایل command >> output.log 2>&1
ترتیب نوشتن
2>&1مهم است. درcommand > file 2>&1، ابتداstdoutبهfileهدایت میشود، سپسstderrبه همان مقصدstdout(که الانfileاست) میرود. اگر برعکس بنویسید (2>&1 > file)،stderrهمچنان به ترمینال میرود.
۵. بازنویسی با استفاده از شمارهی فایل دسکریپتور (File Descriptor)
میتوانید از شمارهی فایل دسکریپتور برای بازنویسی جریانهای دلخواه استفاده کنید:
# ارسال stdout به فایل و همچنین نمایش آن در ترمینال (با tee) command | tee output.log # ارسال stderr به یک فایل و stdout به فایل دیگر command 1> stdout.log 2> stderr.log
۶. بازنویسی با |& (لولهی ترکیبی)
در لولهها (که در بخش بعدی میآید)، |& هر دو جریان stdout و stderr را به دستور بعدی میفرستد:
# هر دو خروجی و خطا به less میروند command |& less
معادل 2>&1 | است.
لولهها (Pipes) — اتصال دستورات به هم
لوله (|) خروجی استاندارد دستور سمت چپ را به ورودی استاندارد دستور سمت راست متصل میکند. این مکانیزم قلب فلسفهی یونیکس است: «هر برنامه یک کار را خوب انجام دهد و خروجی خود را به برنامهی بعدی بدهد».
command1 | command2 | command3
مثالهای عملی:
# پیدا کردن ۵ فایل بزرگ در دایرکتوری جاری du -h * | sort -hr | head -5 # شمارش تعداد خطوطی که حاوی کلمهی "error" در لاگ هستند grep "error" /var/log/syslog | wc -l # نمایش فرآیندهای در حال اجرا با بیشترین مصرف حافظه ps aux | sort -k 4 -r | head -5 # استخراج ستون دوم از یک فایل CSV و مرتبسازی cut -d',' -f2 data.csv | sort | uniq -c
لولهی بینهایت و subshell
هر بخش از یک لوله در یک زیر-شل (subshell) جداگانه اجرا میشود. این موضوع در بخش حلقهها نیز اشاره شد؛ تغییرات متغیرها در یک بخش از لوله، در بخشهای دیگر یا در شل اصلی قابل مشاهده نیست.
# اشتباه: متغیر count در subshell تغییر میکند و در شل اصلی دیده نمیشود count=0 cat file.txt | while read line; do ((count++)); done echo $count # 0 خواهد بود (نه تعداد خطوط) # راهحل: استفاده از process substitution بهجای لوله count=0 while read line; do ((count++)); done < file.txt echo $count # تعداد صحیح خطوط
اینجا-اسناد (Here Documents)
Here Document ساختاری است که به شما اجازه میدهد یک بلوک چندخطی از متن را مستقیماً درون اسکریپت بنویسید و بهعنوان ورودی یک دستور ارسال کنید. این روش برای نوشتن متنهای طولانی، ایجاد فایلهای پیکربندی پویا، یا ارسال ورودی به دستورات تعاملی (مانند bc, ftp, sqlite3) بسیار مفید است.
سینتکس
command << DELIMITER line 1 line 2 ... DELIMITER
-
DELIMITERیک کلمهی دلخواه (معمولاًEOFیاEND) است که نشاندهندهی شروع و پایان بلوک است. -
متن بین دو
DELIMITERبهعنوان ورودی استاندارد بهcommandارسال میشود. -
توجه:
DELIMITERباید کلمهای جداگانه در ابتدای خط باشد و هیچ کاراکتر دیگری قبل/بعد از آن نباشد (بهجز فاصلهها که مجاز نیستند). پایاندهنده باید دقیقاً همان کلمه باشد و در ابتدای خط قرار گیرد.
مثالهای عملی
۱. ارسال متن به cat برای ایجاد فایل:
cat > config.conf << EOF [server] host = localhost port = 8080 debug = true EOF
۲. استفاده از bc برای محاسبات پیچیده:
result=$(bc << EOF scale=2 (10 + 5) / 3 EOF ) echo "نتیجه: $result" # خروجی: 5.00
۳. ارسال دستورات به یک پایگاهداده (مثل sqlite3):
sqlite3 mydb.db << SQL CREATE TABLE users (id INTEGER, name TEXT); INSERT INTO users VALUES (1, 'Ali'); SELECT * FROM users; SQL
گسترش متغیرها و دستورات در Here Document
بهطور پیشفرض، داخل Here Document، گسترش متغیرها ($var) و جانشینی دستورات ($(command)) انجام میشود:
name="Sara" cat << EOF سلام $name، تاریخ امروز: $(date +%Y-%m-%d) EOF
خروجی:
سلام Sara، تاریخ امروز: 2026-08-28
برای جلوگیری از این گسترش (یعنی نگهداشتن $ و ` بهصورت تحتاللفظی)، DELIMITER را در گیومه قرار دهید:
name="Sara" cat << 'EOF' سلام $name، تاریخ امروز: $(date +%Y-%m-%d) EOF
خروجی:
سلام $name، تاریخ امروز: $(date +%Y-%m-%d)
تورفتگی (Indentation) با <<-
اگر <<- بهجای << استفاده شود، تمام تبهای ابتدای خطوط (فقط تب، نه فاصله) نادیده گرفته میشوند. این ویژگی به زیبایی کد درون اسکریپت کمک میکند:
if some_condition; then cat <<- EOF این خط با تب شروع میشود. و این خط نیز. اما در خروجی، تَبها حذف میشوند. EOF fi
اینجا حتماً باید از تب (Tab) استفاده کنید، نه فاصله.
اینجا-رشتهها (Here Strings)
Here String نسخهی سادهتر و تکخطی Here Document است. با استفاده از <<<، یک رشته را بهعنوان ورودی استاندارد به یک دستور میفرستید.
سینتکس
command <<< "string"
مثالهای عملی
۱. ارسال یک رشته به grep:
grep "error" <<< "this is an error message" # خروجی: this is an error message
۲. شمارش کلمات یک رشته بدون استفاده از echo:
wc -w <<< "one two three four" # خروجی: 4
۳. کار با read برای شکافتن یک رشته:
read -r first last <<< "Ali Rezaei" echo "نام: $first, نامخانوادگی: $last"
۴. تبدیل حروف یک رشته:
tr '[:lower:]' '[:upper:]' <<< "hello world" # خروجی: HELLO WORLD
۵. استفاده با bc برای محاسبهی سریع:
echo $(bc <<< "scale=2; 10/3") # خروجی: 3.33
تفاوت Here String با echo |
هر دو روش میتوانند یک رشته را به ورودی یک دستور بفرستند، اما Here String (<<<) کارایی بهتری دارد و از ایجاد یک subshell اضافی جلوگیری میکند:
# روش با echo و لوله (ایجاد subshell) echo "text" | grep "t" # روش با Here String (بدون subshell) grep "t" <<< "text"
Here String همچنین برای متغیرهایی که شامل کاراکترهای خاص هستند امنتر است، چون نیازی به نقلقولگذاری پیچیده ندارد.
ترکیب تکنیکها
مثال جامعی که از بازنویسی، لوله، Here Document و Here String با هم استفاده میکند:
#!/bin/bash # استفاده از Here Document برای ایجاد فایل پیکربندی cat > /tmp/config.ini << EOF [app] name=MyApp version=1.0 EOF # استفاده از Here String برای استخراج مقدار یک کلید version=$(grep "^version=" /tmp/config.ini | cut -d'=' -f2 <<< "$(cat /tmp/config.ini)") # ترکیب لوله و بازنویسی برای پردازش لاگ find /var/log -name "*.log" -exec cat {} \; 2>/dev/null | grep "ERROR" | sort | uniq -c > error_summary.txt echo "پردازش کامل شد. خلاصه در error_summary.txt ذخیره شد."
جدول خلاصهی عملگرها
| عملگر | نام | کاربرد |
|---|---|---|
> | بازنویسی خروجی | نوشتن stdout در فایل (overwrite) |
>> | افزودن خروجی | اضافه کردن stdout به انتهای فایل |
< | بازنویسی ورودی | گرفتن stdin از یک فایل |
2> | بازنویسی خطا | نوشتن stderr در فایل |
2>> | افزودن خطا | اضافه کردن stderr به انتهای فایل |
&> | ترکیب خروجی و خطا | هر دو stdout و stderr را در یک فایل بنویسد |
2>&1 | ارسال stderr به stdout | stderr را به همان مقصد stdout میفرستد |
| | لوله | stdout دستور چپ ← stdin دستور راست |
|& | لولهی ترکیبی | stdout و stderr هر دو ← stdin دستور راست |
<< DELIM | Here Document | یک بلوک چندخطی بهعنوان stdin ارسال میکند |
<<< "str" | Here String | یک رشتهی واحد بهعنوان stdin ارسال میکند |
<<- DELIM | Here Document با حذف تب | تبهای ابتدای خطوط را حذف میکند |
اشتباهات رایج و نکات امنیتی
۱. بازنویسی تصادفی فایلها
استفاده از>بهجای>>میتواند محتوای فایلهای مهم را پاک کند. برای جلوگیری، از آپشنnoclobberدر Bash استفاده کنید:set -o noclobber # جلوگیری از overwrite با > echo "text" > file # اگر file وجود داشته باشد، خطا میدهدبرای اجبار به بازنویسی از
>|استفاده کنید.
۲. فراموشی
2>&1در cron jobs
در اسکریپتهایی که از طریقcronاجرا میشوند، خطاها معمولاً با ایمیل به کاربر ارسال میشوند. برای ذخیرهی خطاها در کنار خروجی، حتماً از2>&1استفاده کنید تا خطاها هم در فایل لاگ ثبت شوند:0 2 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1
۳. استفاده از Here Document با متغیرهای حاوی کاراکترهای خاص
اگر متغیرهای شما شامل$یا`هستند و نمیخواهید گسترش یابند، پایاندهنده را در گیومه قرار دهید.
۴. بازنویسی stdin در حلقهها
وقتی یک حلقه را با بازنویسی<تغذیه میکنید، دقت کنید که اگر درون حلقه ازreadاستفاده میکنید، ورودی آن از همان فایل تأمین میشود و ممکن است باعث تداخل شود. در چنین مواردی از فایل دسکریپتور متفاوت استفاده کنید:while read -u 3 line; do # پردازش خط done 3< data.txt
۵. ایجاد Here Document درون حلقه
اگر Here Document را درون یک حلقه قرار دهید، باید پایاندهنده را در ابتدای خط بیاورید که ممکن است با تورفتگی حلقه تداخل داشته باشد. برای رفع این مشکل از<<-با تبها استفاده کنید.