توی این درس یاد میگیری محتوای فایلها را با ابزار مناسب ببینی: فایل کوچک با cat، فایل بزرگ با less (صفحهبهصفحه و با جستجو)، ابتدا و انتهای فایل با head -n و tail -n، لاگ در حال نوشتهشدن را زنده با tail -f، تعداد خطها را با wc -l و نوع یک فایل ناشناخته را با file. در تمرین ۲۰ خط آخر یک لاگ را میبینی و زنده دنبالش میکنی.
مسئله: فایلها همیشه کوچک نیستند
Section titled “مسئله: فایلها همیشه کوچک نیستند”روی لپتاپ فایلها را در یک ویرایشگر باز میکنی. روی سرور، فایل لاگ ممکن است ۵ گیگابایت باشد و ویرایشگر معمولی آن را باز نمیکند (یا سرور را کند میکند). از طرفی لاگها زنده نوشته میشوند و تو میخواهی ببینی همین الآن چه میگذرد. پس به یک جعبهابزار نیاز داری: یکی برای فایل کوچک، یکی برای بزرگ، یکی برای ابتدا و انتها و یکی برای لاگ زنده.
تشبیه: کتاب بزرگ
Section titled “تشبیه: کتاب بزرگ”برای یک کارتپستال (فایل کوچک) همهاش را یک نگاه میخوانی (cat). برای یک کتاب قطور (فایل بزرگ) ورق میزنی و دنبال کلمه میگردی (less)؛ فقط فهرست و صفحهی آخر را میبینی (head و tail)؛ و برای «روزنامهی زنده» که هر لحظه خبر تازه به انتهایش اضافه میشود، آخرین خط را زیر نظر داری (tail -f).
مثالهای عملی
Section titled “مثالهای عملی”برای تمرین، یک فایل بزرگ لاگمانند (۵۰۰۰ خط) میسازیم. (دستور awk را در درسهای بعد یاد میگیری؛ فعلاً فقط فایل نمونه میسازد.)
awk 'BEGIN{for(i=1;i<=5000;i++) printf "2026-10-03 12:%02d:%02d %s request %d done\n", int(i/60)%60, i%60, (i%7==0?"ERROR":"INFO"), i}' > app.logecho "نکته: برای کار با یک فایل کوچک هم یکی بسازیم"printf 'سلام\nاین یک فایل کوچک است\nسه خط دارد\n' > small.txtls -lh app.log small.txtنکته: برای کار با یک فایل کوچک هم یکی بسازیم-rw-r--r-- 1 ali ali 210K Oct 3 19:58 app.log-rw-r--r-- 1 ali ali 65 Oct 3 19:58 small.txtمثال ۱: فایل کوچک با cat
Section titled “مثال ۱: فایل کوچک با cat”cat (concatenate) محتوای یک یا چند فایل را پشت هم چاپ میکند:
cat small.txtecho "--- دو فایل پشت هم:"printf 'ردیف الف\n' > a.txt; printf 'ردیف ب\n' > b.txtcat a.txt b.txtسلاماین یک فایل کوچک استسه خط دارد--- دو فایل پشت هم:ردیف الفردیف بسه گزینهی مفید:
echo "--- cat -n (شمارهی خط):"cat -n small.txtecho "--- cat -A (نمایش کاراکترهای نامرئی):"printf 'col1\tcol2 \n' | cat -Aecho "--- tac (معکوس):"tac small.txt--- cat -n (شمارهی خط): 1 سلام 2 این یک فایل کوچک است 3 سه خط دارد--- cat -A (نمایش کاراکترهای نامرئی):col1^Icol2 $--- tac (معکوس):سه خط دارداین یک فایل کوچک استسلام-n شمارهی خط میگذارد، -A کاراکترهای ناپیدا را نشان میدهد (^I تب و $ انتهای خط؛ برای پیدا کردن فاصلهی اضافی انتهای خط عالی است) و tac (cat وارونه) خطها را از آخر به اول میدهد. هشدار: cat روی فایل بزرگ یا باینری ترمینال را پر از چیزهای بیمعنی میکند؛ برای فایل بزرگ ابزارهای بعدی را بزن.
مثال ۲: فایل بزرگ با less
Section titled “مثال ۲: فایل بزرگ با less”less فایل را صفحهبهصفحه نشان میدهد و کل فایل را در حافظه نمیریزد (برای فایلهای چند گیگابایتی هم سریع باز میشود). استفاده: less app.log. چون اینجا ترمینال تعاملی نیست، فقط کلیدها را میآورم؛ در ترمینال خودت امتحان کن:
| کلید | کار |
|---|---|
Space / b |
صفحهی بعد / قبل |
j / k یا ↓ / ↑ |
یک خط پایین / بالا |
g / G |
اول / آخر فایل |
/متن + Enter |
جستجو بهسمت پایین (n بعدی، N قبلی) |
?متن |
جستجو بهسمت بالا |
&متن |
فقط خطهای دارای متن را نشان بده |
F |
مثل tail -f (دنبال کردن)؛ با Ctrl+C برگرد |
q |
خروج |
گزینههای مفید هنگام باز کردن: less -N (شمارهی خط)، less -S (خطهای بلند را نشکن؛ با ←/→ بخوان)، less +G file (باز کن و برو آخر)، less +/ERROR file (باز کن و به اولین ERROR برو). less همان برنامهای است که man برای نمایش صفحهها استفاده میکند (درس قبل)، پس کلیدها یکساناند. (اگر less را در pipe یا بدون ترمینال بزنی، مثل cat عمل میکند.)
در less برای جستجوی کلمهی ERROR چه مینویسی؟
با / جستجو میکنی، با n نتیجهی بعدی و با q خارج میشوی.
مثال ۳: ابتدا و انتها، head و tail
Section titled “مثال ۳: ابتدا و انتها، head و tail”پیشفرض هر دو ۱۰ خط است. با -n تعداد را میدهی:
echo "--- head -n 3 (سه خط اول):"head -n 3 app.logecho "--- tail -n 3 (سه خط آخر):"tail -n 3 app.log--- head -n 3 (سه خط اول):2026-10-03 12:00:01 INFO request 1 done2026-10-03 12:00:02 INFO request 2 done2026-10-03 12:00:03 INFO request 3 done--- tail -n 3 (سه خط آخر):2026-10-03 12:23:18 ERROR request 4998 done2026-10-03 12:23:19 INFO request 4999 done2026-10-03 12:23:20 INFO request 5000 doneچند شکل دیگر که خیلی به کار میآید:
echo "--- tail -n +4998 (از خط ۴۹۹۸ تا آخر):"tail -n +4998 app.logecho "--- head -n -4997 (همه بهجز ۴۹۹۷ خط آخر):"head -n -4997 app.logecho "--- head -c 24 (۲۴ بایت اول):"head -c 24 app.log; echo--- tail -n +4998 (از خط ۴۹۹۸ تا آخر):2026-10-03 12:23:18 ERROR request 4998 done2026-10-03 12:23:19 INFO request 4999 done2026-10-03 12:23:20 INFO request 5000 done--- head -n -4997 (همه بهجز ۴۹۹۷ خط آخر):2026-10-03 12:00:01 INFO request 1 done2026-10-03 12:00:02 INFO request 2 done2026-10-03 12:00:03 INFO request 3 done--- head -c 24 (۲۴ بایت اول):2026-10-03 12:00:01 INFO-n +N یعنی «از خط N به بعد»، -n -N (برای head) یعنی «همه بهجز N خط آخر» و -c تعداد بایت. میتوانی همزمان چند فایل بدهی؛ با اسم فایل جدا میشوند:
head -n 1 a.txt b.txt small.txt==> a.txt <==ردیف الف
==> b.txt <==ردیف ب
==> small.txt <==سلامبرای دیدن خطهای وسط فایل (مثلاً ۱۰۰ تا ۱۰۲) ترکیب head و tail یا sed -n (درسهای بعد) کار میکند:
head -n 102 app.log | tail -n 32026-10-03 12:01:40 INFO request 100 done2026-10-03 12:01:41 INFO request 101 done2026-10-03 12:01:42 INFO request 102 doneمثال ۴: لاگ زنده با tail -f
Section titled “مثال ۴: لاگ زنده با tail -f”لاگها مدام به انتها اضافه میشوند. tail -f (follow) انتها را نشان میدهد و منتظر میماند تا خطهای تازه بیایند. در ترمینال تعاملی با Ctrl+C خارج میشوی. اینجا یک «نویسندهی» پسزمینه میگذاریم که هر ۰.۷ ثانیه یک خط به live.log اضافه میکند، و timeout بعد از ۴ ثانیه tail را قطع میکند:
printf 'شروع سرویس\nآماده برای درخواست\n' > live.log( for i in 1 2 3; do sleep 0.7; echo "درخواست جدید شماره $i" >> live.log; done ) &timeout 4 tail -n 2 -f live.logecho "(با timeout قطع شد؛ در ترمینال واقعی Ctrl+C میزنی)"شروع سرویسآماده برای درخواستدرخواست جدید شماره 1درخواست جدید شماره 2درخواست جدید شماره 3(با timeout قطع شد؛ در ترمینال واقعی Ctrl+C میزنی)اول دو خط آخر موجود را نشان داد (-n 2)، بعد هر خط تازه بهمحض نوشته شدن ظاهر شد. این همان ابزاری است که موقع عیبیابی سرور بیشتر از همه میزنی. برای دنبال کردن چند فایل همزمان: tail -f a.log b.log.
اگر لاگ چرخانده شود (logrotate فایل قدیمی را تغییر نام میدهد و یک فایل تازه با همان اسم میسازد)، یک نکتهی مهم هست: tail -f به خود فایل (inode) چسبیده است، نه به اسم. ببین:
printf 'خط قدیمی\n' > rot.log( sleep 1; mv rot.log rot.log.1; echo "فایل تازه با همان اسم" > rot.log; sleep 0.5; echo "ادامهی نوشتن در فایل قدیمی" >> rot.log.1 ) &timeout 3 tail -f rot.logecho "--- شمارهی inode ها:"ls -i rot.log rot.log.1خط قدیمیادامهی نوشتن در فایل قدیمی--- شمارهی inode ها:113277 rot.log113276 rot.log.1بعد از چرخش، tail -f همچنان فایل قدیمی (حالا rot.log.1) را دنبال کرد و خطهای فایل تازه (rot.log) را نشان نداد. ls -i نشان میدهد این دو اسم دو inode متفاوتاند. راهحل برای لاگهای چرخان tail -F (حرف بزرگ) است که طبق مستندات GNU معادل --follow=name --retry است: اسم را دنبال میکند و بعد از چرخش به فایل تازه میرود. (در محیط آزمایشی من رفتار -F پایدار نبود، پس اجرایش را اینجا نشان نمیدهم؛ روی سرور خودت امتحانش کن.)
مثال ۵: شمارش، wc
Section titled “مثال ۵: شمارش، wc”wc (word count) خط، کلمه و بایت را میشمارد:
wc small.txtecho "--- فقط خطها:"wc -l small.txt app.logecho "--- کلمهها (-w)، بایتها (-c):"wc -w small.txtwc -c small.txt 3 9 65 small.txt--- فقط خطها: 3 small.txt 5000 app.log 5003 total--- کلمهها (-w)، بایتها (-c):9 small.txt65 small.txtستونهای wc پیشفرض: خط، کلمه، بایت، نام. wc -l رایجترین است: «چند خط؟» (تعداد خطای لاگ، تعداد کاربر و…). توجه: wc -c بایت میشمارد؛ فارسی در UTF-8 هر حرف ۲ بایت است، و wc -m تعداد کاراکتر را میدهد (در locale مناسب).
برای نتیجهی تمیز (فقط عدد) از ورودی استاندارد بخوان تا اسم فایل چاپ نشود: wc -l < app.log.
wc -l < app.log5000مثال ۶: این فایل چیست؟ file
Section titled “مثال ۶: این فایل چیست؟ file”اسم فایل (.txt، .jpg) فقط یک قرارداد است. file محتوای واقعی را بررسی میکند:
printf '#!/bin/bash\necho hi\n' > script.shgzip -k small.txt: > empty.datfile small.txt app.log script.sh small.txt.gz empty.dat /bin/ls /etc /binsmall.txt: Unicode text, UTF-8 textapp.log: ASCII textscript.sh: Bourne-Again shell script, ASCII text executablesmall.txt.gz: gzip compressed data, was "small.txt", last modified: Sat Oct 3 19:58:50 2026, from Unix, original size modulo 2^32 65empty.dat: empty/bin/ls: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=00a639537eef557c0dcb20ad52892bc84863ed94, for GNU/Linux 3.7.0, stripped/etc: directory/bin: symbolic link to usr/binنتیجه: متن UTF-8، متن ASCII، اسکریپت bash، فایل gzip، فایل خالی، برنامهی اجرایی ELF، پوشه و لینک نمادین. قبل از اینکه cat بزنی، file بزن؛ اگر data یا ELF یا gzip دید، cat نکن.
مثال ۷: فایل باینری را چطور نگاه کنیم؟
Section titled “مثال ۷: فایل باینری را چطور نگاه کنیم؟”اگر cat روی فایل باینری بزنی، ترمینال پر از علامتهای عجیب میشود (و گاهی خراب). بهجایش:
echo "--- ۱۶ بایت اول برنامهی ls (هگز):"head -c 16 /bin/ls | od -An -tx1echo "--- رشتههای خوانا داخل برنامه (strings):"strings /bin/ls | head -4--- ۱۶ بایت اول برنامهی ls (هگز): 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00--- رشتههای خوانا داخل برنامه (strings):/lib/ld-linux-aarch64.so.1__libc_start_main__cxa_finalizeabortod (octal dump) بایتها را به شکل عدد نشان میدهد؛ 7f 45 4c 46 در ابتدا یعنی «ELF» (امضای برنامههای لینوکس). strings فقط متنهای خوانا را بیرون میکشد. (اگر ترمینالت بعد از cat یک باینری به هم ریخت، دستور reset را بزن.)
مثال ۸: ترکیب: ببین و بشمار
Section titled “مثال ۸: ترکیب: ببین و بشمار”echo "تعداد کل خطها: $(wc -l < app.log)"echo "۳ خط اول:"; head -n 3 app.logecho "فقط خطهای ERROR (تعداد): $(grep -c ERROR app.log)"echo "۲ خط آخر ERROR:"; grep ERROR app.log | tail -n 2تعداد کل خطها: 5000۳ خط اول:2026-10-03 12:00:01 INFO request 1 done2026-10-03 12:00:02 INFO request 2 done2026-10-03 12:00:03 INFO request 3 doneفقط خطهای ERROR (تعداد): 714۲ خط آخر ERROR:2026-10-03 12:23:11 ERROR request 4991 done2026-10-03 12:23:18 ERROR request 4998 doneدستورهای کوچک را میشود با هم ترکیب کرد (grep و | در بخش بعدی). اینجا یک پیشنمایش: grep ERROR فقط خطهای دارای ERROR را میدهد و tail آخرینها را.
پشت پرده: چرا tail سریع است و cat نه؟
Section titled “پشت پرده: چرا tail سریع است و cat نه؟”tail -n 5 hugefile فایل را از انتها میخواند: هسته اجازه میدهد با یک عدد جابهجایی (lseek) مستقیم به آخرین چند کیلوبایت بپری، بدون خواندن گیگابایتهای قبلی. head هم فقط ابتدا را میخواند و بعد خارج میشود. اما cat و wc -l باید کل فایل را بخوانند. less هم لازم نیست همه را بخواند و تا جایی که ببینی میخواند.
برای tail -f: برنامه فایل را باز نگه میدارد و هر چند ثانیه، یا با اعلان «تغییر فایل» هسته (inotify)، میپرسد «چیزی اضافه شده؟». چون به فایل باز (نه اسم) چسبیده، بعد از چرخش لاگ هنوز فایل قدیمی را میخواند؛ -F اسم را دوباره باز میکند. و هرگز cat bigfile را روی لاگ چندگیگابایتی نزن؛ less یا tail.
جدولهای مرجع
Section titled “جدولهای مرجع”| دستور | کار | نمونه |
|---|---|---|
cat f |
چاپ همهی محتوا | cat small.txt |
cat -n f |
با شمارهی خط | |
cat -A f |
نمایش کاراکترهای نامرئی | |
tac f |
خطها از آخر به اول | |
less f |
صفحهبهصفحه با جستجو | less +G app.log |
head -n N f |
N خط اول | head -n 5 app.log |
tail -n N f |
N خط آخر | tail -n 20 app.log |
tail -n +N f |
از خط N تا آخر | |
tail -f f |
آخر + دنبال کردن زنده | |
tail -F f |
دنبال کردن با اسم (امن برای چرخش) | |
wc -l f |
تعداد خط | wc -l < app.log |
file f |
نوع فایل | file /bin/ls |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) cat روی فایل بزرگ
Section titled “۱) cat روی فایل بزرگ”echo "اینجا فقط ۱۰ خط اول را میبینیم، نه ۵۰۰۰ خط:"cat app.log | head -n 3اینجا فقط ۱۰ خط اول را میبینیم، نه ۵۰۰۰ خط:2026-10-03 12:00:01 INFO request 1 done2026-10-03 12:00:02 INFO request 2 done2026-10-03 12:00:03 INFO request 3 donecat app.log تمام ۵۰۰۰ خط (در واقعیت ملیونها) را میریزد. راهحل: less، یا head/tail. (اینجا از | برای محدود کردن خروجی استفاده کردم.)
۲) فراموش کردن -f (و اعتماد به یک عکس لحظهای)
Section titled “۲) فراموش کردن -f (و اعتماد به یک عکس لحظهای)”tail -n 20 فقط وضعیت همین لحظه است؛ خطهای بعدی را نمیبینی. راهحل: tail -f یا tail -F وقتی «زنده» میخواهی.
۳) tail -f روی لاگ چرخاندهشده
Section titled “۳) tail -f روی لاگ چرخاندهشده”tail -f به فایل قدیمی چسبیده میماند (مثال ۴: خط تازهی فایل جدید دیده نشد). راهحل: tail -F.
۴) شمردن با wc -l و گمان اینکه خط آخر بدون newline هم شمرده میشود
Section titled “۴) شمردن با wc -l و گمان اینکه خط آخر بدون newline هم شمرده میشود”printf 'a\nb\nc' > nonl.txtwc -l nonl.txtprintf 'a\nb\nc\n' > nl.txtwc -l nl.txt2 nonl.txt3 nl.txtwc -l تعداد کاراکترهای newline را میشمارد؛ فایلی که آخرش \n ندارد یکی کمتر نشان میدهد (۲ نه ۳). راهحل: بدان این رفتار را؛ grep -c '' همهی خطها را میشمارد.
۵) باز کردن فایل باینری با ویرایشگر یا cat
Section titled “۵) باز کردن فایل باینری با ویرایشگر یا cat”file /bin/ls | cut -c1-40/bin/ls: ELF 64-bit LSB pie executable,قبل از باز کردن، file را بزن. فایل باینری را با cat نخوان؛ از hexdump/od/strings استفاده کن.
۵ خط اول و ۵ خط آخر app.log را نشان بده و بگو فایل چند خط دارد.
دیدن جواب
awk 'BEGIN{for(i=1;i<=5000;i++) printf "2026-10-03 12:%02d:%02d %s request %d done\n", int(i/60)%60, i%60, (i%7==0?"ERROR":"INFO"), i}' > app.loghead -n 5 app.logecho "..."tail -n 5 app.logecho "تعداد خطها: $(wc -l < app.log)"2026-10-03 12:00:01 INFO request 1 done2026-10-03 12:00:02 INFO request 2 done2026-10-03 12:00:03 INFO request 3 done2026-10-03 12:00:04 INFO request 4 done2026-10-03 12:00:05 INFO request 5 done...2026-10-03 12:23:16 INFO request 4996 done2026-10-03 12:23:17 INFO request 4997 done2026-10-03 12:23:18 ERROR request 4998 done2026-10-03 12:23:19 INFO request 4999 done2026-10-03 12:23:20 INFO request 5000 doneتعداد خطها: 5000تمرین اصلی: ۲۰ خط آخر یک فایل لاگ را ببین و زنده دنبالش کن. یک لاگ بساز، یک نویسندهی پسزمینه بگذار که چند خط جدید بنویسد، و با tail -n 20 -f (و timeout) هر دو بخش را نشان بده. بعد با tail -n 1 فقط آخرین خط را بگیر.
دیدن جواب
seq 1 30 | sed 's/^/خط /' > service.log( for i in 31 32 33; do sleep 0.6; echo "خط $i (تازه)" >> service.log; done ) &timeout 3 tail -n 20 -f service.log | tail -n 6echo "--- آخرین خط: $(tail -n 1 service.log)"خط 28خط 29خط 30خط 31 (تازه)خط 32 (تازه)خط 33 (تازه)--- آخرین خط: خط 33 (تازه)(با | tail -n 6 فقط انتهای خروجی را نشان دادم تا کوتاه شود؛ در ترمینال واقعی ۲۰ خط و بعد خطهای تازه را میبینی.)
یک تابع watch-errors بنویس که یک فایل لاگ بگیرد و ۳ کار کند: تعداد کل خطها، تعداد خطهای ERROR، و ۲ خط آخر ERROR. روی app.log امتحانش کن؛ در پایان با file نشان بده نوع فایل چیست.
دیدن جواب
awk 'BEGIN{for(i=1;i<=5000;i++) printf "2026-10-03 12:%02d:%02d %s request %d done\n", int(i/60)%60, i%60, (i%7==0?"ERROR":"INFO"), i}' > app.logwatch-errors() { echo "کل خطها: $(wc -l < "$1")" echo "خطهای ERROR: $(grep -c ERROR "$1")" echo "دو ERROR آخر:"; grep ERROR "$1" | tail -n 2}watch-errors app.logfile app.logکل خطها: 5000خطهای ERROR: 714دو ERROR آخر:2026-10-03 12:23:11 ERROR request 4991 done2026-10-03 12:23:18 ERROR request 4998 doneapp.log: ASCII textآزمونک
Section titled “آزمونک”برای فایل لاگ ۳ گیگابایتی کدام مناسب است؟
cat کل فایل را میریزد؛ less و tail سریعاند.
tail -f چه میکند؟
با Ctrl+C خارج میشوی.
تفاوت tail -f و tail -F؟
برای لاگهای چرخان از -F استفاده کن.
wc -l چه چیزی را میشمارد؟
wc -w کلمه و wc -c بایت.
قبل از cat روی فایل ناشناخته چه کار میکنی؟
cat روی باینری ترمینال را به هم میریزد.
tail -n +4998 app.log چه میدهد؟
+N یعنی از خط N به بعد.
جمعبندی
Section titled “جمعبندی”- فایل کوچک:
cat(همچنین-nبرای شمارهی خط و-Aبرای دیدن کاراکترهای نامرئی)؛ فایل بزرگ:less(/جستجو،G،q). - ابتدا و انتها:
head -n Nوtail -n N(-n +Nیعنی از خط N،-cبایت). - لاگ زنده:
tail -f، وtail -Fوقتی لاگ چرخانده میشود. wc -lتعداد خط (با< fileبرای خروجی تمیز)،fileنوع واقعی فایل.catروی فایل بزرگ یا باینری خطرناک است؛fileقبل از خواندن، وod/stringsبرای باینری.tailوheadسریعاند چون کل فایل را نمیخوانند.
| دستور | کاری که میکند |
|---|---|
cat file | چاپ همهی فایل کوچک |
cat -n file | با شمارهی خط |
less file (و q برای خروج) | خواندن صفحهبهصفحه |
less +G file | باز کردن و رفتن به آخر |
head -n 5 file | ۵ خط اول |
tail -n 20 file | ۲۰ خط آخر |
tail -n +100 file | از خط ۱۰۰ تا آخر |
tail -f file | دنبال کردن زنده |
tail -F file | دنبال کردن با اسم (چرخش لاگ) |
wc -l < file | تعداد خطها |
file name | نوع فایل |
head -c 16 f | od -An -tx1 | بایتهای اول به شکل هگز |