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

ترمینال و shell

توی این درس یاد می‌گیری وقتی چیزی را در ترمینال تایپ می‌کنی دقیقاً چه کسی آن را می‌خواند و اجرا می‌کند: ترمینال (پنجره) و shell (مفسر دستورها) دو چیز جدا هستند. bash و zsh را می‌شناسی، ساختار یک دستور را (command، option، argument) می‌خوانی، می‌فهمی quote ها (' و ") برای چه‌اند، از تاریخچه و Tab برای سریع‌تر شدن استفاده می‌کنی و میانبرهای حیاتی Ctrl+C، Ctrl+R و Ctrl+L را یاد می‌گیری. دستورها: echo، whoami، history و clear.

مسئله: صفحه‌ی سیاه و یک خط چشمک‌زن

Section titled “مسئله: صفحه‌ی سیاه و یک خط چشمک‌زن”

اولین بار که ترمینال را باز می‌کنی فقط یک خط چشمک‌زن می‌بینی و هیچ دکمه‌ای نیست. هر چیزی را که تایپ کنی، یک «چیزی» می‌خواند و کاری می‌کند. آن «چیز» کیست؟ اگر ندانی، خطاهایی مثل command not found یا رفتار عجیب فاصله‌ها و quote ها گیجت می‌کند. این درس نقشه‌ی کار را می‌دهد.

ترمینال مثل گیشه‌ی بانک است: پنجره‌ای که با آن حرف می‌زنی و جواب می‌بینی. shell مثل کارمندی است که پشت گیشه نشسته: حرف تو را می‌فهمد، به کارهای واقعی (برنامه‌ها) تقسیم می‌کند و نتیجه را برمی‌گرداند. گیشه را می‌شود عوض کرد (چند برنامه‌ی ترمینال مختلف) بدون اینکه کارمند عوض شود، و برعکس.

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

echo هر چه به آن بدهی را چاپ می‌کند. ساده‌ترین دستور برای فهمیدن ساختار:

Terminal window
echo hello
echo "سلام دنیا"
echo یک دو سه
خروجی
hello
سلام دنیا
یک دو سه

دستور اول echo است (command) و hello یک argument. در سومی سه argument داریم؛ ولی خروجی آن‌ها را با یک فاصله چاپ کرد. بعد می‌بینی فاصله‌ها چه نقشی دارند.

مثال ۲: ساختار دستور، command و option و argument

Section titled “مثال ۲: ساختار دستور، command و option و argument”

هر دستور سه نوع بخش دارد: command (چه کاری)، option (چطور؛ با - یا --) و argument (روی چه چیزی). با ls که فهرست فایل‌ها را می‌دهد:

Terminal window
ls /etc/os-release
ls -l /etc/os-release
ls --long /etc/os-release 2>&1 | head -1
ls -l -h /etc/os-release
ls -lh /etc/os-release
خروجی
/etc/os-release
lrwxrwxrwx 1 root root 21 Sep 6 14:29 /etc/os-release -> ../usr/lib/os-release
ls: unrecognized option '--long'
lrwxrwxrwx 1 root root 21 Sep 6 14:29 /etc/os-release -> ../usr/lib/os-release
lrwxrwxrwx 1 root root 21 Sep 6 14:29 /etc/os-release -> ../usr/lib/os-release
  • ls /etc/os-release: command + یک argument (فایل).
  • -l یک option کوتاه (یک حرف) برای «فرمت بلند»؛ ls --long وجود ندارد (option بلند باید -- و اسم کامل باشد و هر دستور اسم‌های خودش را دارد).
  • گزینه‌های کوتاه را می‌شود چسباند: -l -h همان -lh است.
بخش مثال معنی
command ls کدام برنامه اجرا شود
option کوتاه -l، -h رفتار برنامه را عوض می‌کند (حرف تکی، بعد از -)
option بلند --help، --all همان با اسم کامل (بعد از --)
argument /etc/os-release داده/فایلی که برنامه رویش کار می‌کند

مثال ۳: ترمینال و shell، کدام است؟

Section titled “مثال ۳: ترمینال و shell، کدام است؟”

شل فعلی را بپرس:

Terminal window
echo "نام shell من: $SHELL"
echo "پروسه‌ی فعلی: $(ps -p $$ -o comm=)"
echo "نسخه: $(bash --version | head -1)"
خروجی
نام shell من: /bin/bash
پروسه‌ی فعلی: bash
نسخه: GNU bash, version 5.2.21(1)-release (aarch64-unknown-linux-gnu)

$SHELL شل پیش‌فرض کاربر است و $$ شماره‌ی پروسه‌ی همین shell. فهرست shell های معتبر سیستم هم در یک فایل است:

Terminal window
cat /etc/shells
خروجی
# /etc/shells: valid login shells
/bin/sh
/usr/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/usr/bin/dash
/bin/zsh
/usr/bin/zsh
/usr/bin/zsh

bash (Bourne Again SHell) پیش‌فرض اکثر توزیع‌ها (از جمله Ubuntu) است و این دوره روی آن نوشته شده. zsh (روی مک‌های جدید پیش‌فرض است) شبیه bash است با امکانات بیشتر در تکمیل و شخصی‌سازی. هر دو را می‌توان روی یک سیستم داشت:

Terminal window
bash -c 'echo "bash اجرا کرد؛ نسخه $BASH_VERSION"'
zsh -c 'echo "zsh اجرا کرد؛ نسخه $ZSH_VERSION"'
خروجی
bash اجرا کرد؛ نسخه 5.2.21(1)-release
zsh اجرا کرد؛ نسخه 5.9

دستورهای ساده (ls، cd، echo) در هر دو یکسان‌اند؛ تفاوت‌ها در اسکریپت‌نویسی و شخصی‌سازی دیده می‌شود. (sh و dash هم shell های ساده‌ترند که برای اسکریپت‌های سیستمی به کار می‌روند.)

⚡ بررسی سریع

ترمینال و shell چه فرقی دارند؟

مثال ۴: فاصله، quote و خرد کردن توسط shell

Section titled “مثال ۴: فاصله، quote و خرد کردن توسط shell”

shell دستور را با فاصله خرد می‌کند. برای اینکه فاصله جزو یک argument باشد، باید quote بگذاری:

Terminal window
mkdir -p t && cd t
touch "my file.txt"
ls
echo "--- بدون quote (دو argument):"
ls my file.txt 2>&1
echo "--- با quote (یک argument):"
ls "my file.txt"
خروجی
my file.txt
--- بدون quote (دو argument):
ls: cannot access 'my': No such file or directory
ls: cannot access 'file.txt': No such file or directory
--- با quote (یک argument):
my file.txt

بدون quote، shell my و file.txt را دو فایل جدا دید و خطا داد. نکته‌ی مهم: خطا از ls است، ولی علتش از تجزیه‌ی shell. فرق دو نوع quote:

Terminal window
name="علی"
echo "سلام $name"
echo 'سلام $name'
echo "امروز: $(date +%Y)"
echo 'امروز: $(date +%Y)'
خروجی
سلام علی
سلام $name
امروز: 2026
امروز: $(date +%Y)

داخل "..." (دابل) متغیر ($name) و $(...) جایگزین می‌شوند؛ داخل '...' (سینگل) همه‌چیز عیناً می‌ماند. قاعده‌ی عملی: پیش‌فرض "..."؛ '...' وقتی نمی‌خواهی هیچ‌چیز تفسیر شود.

مثال ۵: چه کسی هستم؟ whoami

Section titled “مثال ۵: چه کسی هستم؟ whoami”
Terminal window
whoami
id -un
echo $USER
خروجی
ali
ali

هر سه نام کاربر فعلی را می‌دهند (دو تا اول دستورند، سومی یک متغیر محیطی). whoami در اسکریپت‌ها و وقتی با sudo کار می‌کنی (sudo whoami را در درس root ببین) مهم است.

shell دستورهای قبلی را نگه می‌دارد. در یک ترمینال تعاملی، فقط history را می‌زنی. اینجا (که اسکریپتی اجرا می‌شود) چند دستور را دستی به تاریخچه می‌دهیم تا خروجی‌اش را ببینی:

Terminal window
HISTFILE=/dev/null; set -o history
history -c
history -s "ls -l /etc"
history -s "cd /var/log"
history -s "echo سلام"
history
خروجی
1 ls -l /etc
2 cd /var/log
3 echo سلام
4 history

هر دستور یک شماره دارد. در ترمینال تعاملی از آن استفاده می‌کنی: !! (دستور قبلی را دوباره اجرا کن)، !42 (دستور شماره‌ی ۴۲)، !ls (آخرین دستوری که با ls شروع شد)، و مهم‌تر از همه Ctrl+R: جستجوی معکوس (مثال ۸). اندازه‌ی تاریخچه در تنظیمات bash است:

Terminal window
grep -E '^HIST(SIZE|FILESIZE|CONTROL)' ~/.bashrc
خروجی
HISTCONTROL=ignoreboth
HISTSIZE=1000
HISTFILESIZE=2000

HISTSIZE تعداد دستورهای حافظه، HISTFILESIZE تعداد در فایل ~/.bash_history و HISTCONTROL=ignoreboth یعنی دستورهایی که با فاصله شروع می‌شوند یا تکراری‌اند ثبت نشوند. هشدار: رمزها را روی خط فرمان ننویس؛ تاریخچه آن‌ها را در فایل ذخیره می‌کند.

کلید Tab نام دستور و فایل را کامل می‌کند؛ اگر چند گزینه باشد، با دو بار Tab فهرست را نشان می‌دهد. چیزی که پشت Tab کار می‌کند را می‌توانیم مستقیم ببینیم، دستور compgen:

Terminal window
echo "دستورهایی که با lsb شروع می‌شوند:"
compgen -c lsb | sort -u
echo "--- فایل‌های /etc که با host شروع می‌شوند:"
compgen -f /etc/host
خروجی
دستورهایی که با lsb شروع می‌شوند:
lsb_release
lsblk
--- فایل‌های /etc که با host شروع می‌شوند:
/etc/hostname
/etc/hosts
/etc/host.conf
/etc/hosts.allow
/etc/hosts.deny

اگر lsb و Tab بزنی، shell همین فهرست را می‌داند و تا جایی که یکتا است کامل می‌کند. استفاده‌ی درست از Tab سرعت را چند برابر می‌کند و غلط تایپی را از بین می‌برد؛ هر وقت اسمی طولانی دیدی، Tab بزن.

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

میانبر کار
Ctrl+C قطع کردن دستور در حال اجرا (سیگنال SIGINT)
Ctrl+D پایان ورودی؛ در خط خالی ترمینال را می‌بندد (exit)
Ctrl+L پاک کردن صفحه (مثل clear)
Ctrl+R جستجو در تاریخچه (بنویس و Enter بزن؛ دوباره Ctrl+R برای قبلی)
Ctrl+A / Ctrl+E رفتن به اول / آخر خط
Ctrl+U / Ctrl+K پاک کردن تا اول / تا آخر خط
Ctrl+W پاک کردن کلمه‌ی قبل از مکان‌نما
Ctrl+Z متوقف کردن موقت دستور (درس پروسه‌ها)
↑ / ↓ دستورهای قبلی/بعدی

Ctrl+C همان سیگنال INT است. می‌توانیم اثرش را ببینیم: timeout دستوری را با سیگنال انتخابی قطع می‌کند:

Terminal window
timeout -s INT 1 sleep 10
echo "کد خروج: $?"
خروجی
کد خروج: 124

دستور sleep 10 بعد از ۱ ثانیه با سیگنال INT قطع شد و کد خروج 124 (کدی که timeout برای «قطع شد» برمی‌گرداند) آمد. و clear چه می‌کند؟ فقط یک رشته‌ی کنترلی برای ترمینال می‌فرستد:

Terminal window
TERM=xterm clear | od -c | head -2
خروجی
0000000 033 [ H 033 [ 2 J 033 [ 3 J
0000013

033 [ H 033 [ 2 J یعنی «مکان‌نما را به بالا ببر و صفحه را پاک کن». پس clear برنامه‌ای است که به ترمینال دستور می‌دهد، نه کاری روی فایل‌ها.

مثال ۹: کد خروج، دستور موفق شد یا نه؟

Section titled “مثال ۹: کد خروج، دستور موفق شد یا نه؟”

هر دستور بعد از اجرا یک عدد برمی‌گرداند: ۰ یعنی موفق، غیر صفر یعنی خطا. shell آن را در $? نگه می‌دارد:

Terminal window
true; echo "true → $?"
false; echo "false → $?"
ls /etc/os-release > /dev/null; echo "ls فایل موجود → $?"
ls /nonexistent 2>/dev/null; echo "ls فایل ناموجود → $?"
خروجی
true → 0
false → 1
ls فایل موجود → 0
ls فایل ناموجود → 2

این عدد در اسکریپت‌ها و ترکیب دستورها (&& و || در درس‌های بعد) اساس تصمیم‌گیری است.

پشت پرده: shell چه می‌کند؟

Section titled “پشت پرده: shell چه می‌کند؟”

وقتی ls -l /etc را می‌نویسی و Enter می‌زنی، shell این مراحل را می‌رود: ۱) خرد کردن خط با فاصله به کلمه‌ها (و قانون quote)؛ ۲) جایگزینی‌ها: متغیرها ($name)، $(...)، و wildcard ها (*)؛ ۳) پیدا کردن دستور: اول builtin های خود shell (مثل cd، echo)، بعد برنامه‌ای با آن نام در پوشه‌های $PATH؛ ۴) fork: یک کپی از shell ساخته می‌شود؛ ۵) کپی با exec برنامه‌ی ls را جایگزین خودش می‌کند؛ ۶) shell منتظر می‌ماند تا تمام شود و کد خروج را در $? ذخیره می‌کند.

همین است که cd باید builtin باشد: اگر برنامه‌ی جدا بود، فقط پوشه‌ی کپی را عوض می‌کرد نه shell اصلی را. و همین است که فاصله و quote اهمیت دارند: تجزیه قبل از اجرای برنامه انجام می‌شود، پس خود برنامه (مثل ls) فقط argument های آماده می‌گیرد.

دستور کار مثال
echo متن چاپ متن echo "سلام"
echo -n بدون خط جدید echo -n "بدون newline"
whoami نام کاربر فعلی whoami
history فهرست دستورهای قبلی history | tail -5
clear پاک کردن صفحه clear یا Ctrl+L
echo $? کد خروج دستور قبلی ls /x; echo $?
compgen -c prefix دستورهایی که با پیشوند شروع می‌شوند compgen -c lsb
نوع quote متغیر و $(...) کاربرد
"..." جایگزین می‌شوند پیش‌فرض، وقتی فاصله هست
'...' عیناً می‌ماند وقتی نمی‌خواهی چیزی تفسیر شود
بدون quote جایگزین و خرد می‌شوند فقط وقتی مطمئنی فاصله/علامت خاص نیست

۱) فاصله در اسم فایل بدون quote

Section titled “۱) فاصله در اسم فایل بدون quote”

مثال ۴: ls my file.txt دو فایل می‌بیند. راه‌حل: quote، یا \ (بک‌اسلش) قبل از فاصله: ls my\ file.txt.

۲) دستور نوشته‌شده با حروف اشتباه

Section titled “۲) دستور نوشته‌شده با حروف اشتباه”
Terminal window
lss -l
خروجی
bash: lss: command not found

اسم دستور اشتباه است و shell چنین برنامه‌ای پیدا نمی‌کند: command not found. راه‌حل: با Tab کامل کن؛ بین ls و l فرق هست و حروف کوچک/بزرگ مهم‌اند (LS هم پیدا نمی‌شود).

۳) فاصله دور علامت = در تعریف متغیر

Section titled “۳) فاصله دور علامت = در تعریف متغیر”
Terminal window
name = "علی"
خروجی
bash: name: command not found

در shell نباید دور = فاصله باشد. با فاصله، name به‌عنوان یک دستور اجرا می‌شود. راه‌حل: name="علی".

۴) گیر کردن در یک دستور که تمام نمی‌شود

Section titled “۴) گیر کردن در یک دستور که تمام نمی‌شود”

بعضی دستورها (مثل cat بدون فایل یا sleep 1000) تمام نمی‌شوند و مکان‌نما برنمی‌گردد. راه‌حل: Ctrl+C. (برای cat بدون فایل، Ctrl+D هم پایان ورودی می‌دهد.)

۵) paste کردن دستورهایی که نمی‌فهمی

Section titled “۵) paste کردن دستورهایی که نمی‌فهمی”
Terminal window
echo 'rm -rf /' | cat
خروجی
rm -rf /

دستورهای خطرناک را از اینترنت مستقیم paste نکن؛ بالا فقط آن را «چاپ» کردیم، ولی اگر اجرا می‌شد می‌توانست همه‌چیز را پاک کند. راه‌حل: اول echo یا man بزن و بفهم هر بخش چه می‌کند.

✎ تمرینآسان

دستور echo را سه بار با سه argument مختلف اجرا کن؛ بعد با whoami نام کاربرت را بگو و با echo $? ببین whoami موفق بوده یا نه.

دیدن جواب
Terminal window
echo یک
echo "دو و سه"
echo 'چهار $HOME'
whoami
echo "کد خروج whoami: $?"
خروجی
یک
دو و سه
چهار $HOME
ali
کد خروج whoami: 0
✎ تمرینمتوسط

تمرین اصلی: با Ctrl+R یک دستور قدیمی را پیدا و دوباره اجرا کن. چون اینجا ترمینال تعاملی نیست، همان مفهوم را با تاریخچه‌ی ساختگی انجام بده: سه دستور به history بده، با history | grep یکی را پیدا کن و شماره‌اش را بگیر. (در ترمینال واقعی: Ctrl+R بزن، چند حرف از دستور را بنویس، Enter.)

دیدن جواب
Terminal window
HISTFILE=/dev/null; set -o history; history -c
history -s "ls -l /etc"
history -s "cd /var/log"
history -s "echo پروژه‌ی من"
history | grep "var/log"
خروجی
2 cd /var/log
4 history | grep "var/log"

در ترمینال واقعی با Ctrl+R و نوشتن var/log همین دستور را می‌یابی و با Enter دوباره اجرا می‌کنی؛ یا شماره‌اش را با !2 صدا می‌زنی.

✎ تمرینسخت

یک فایل به اسم my report 2026.txt (با فاصله) بساز، سه روش مختلف برای اشاره به آن در ls بنویس (دابل‌quote، سینگل‌quote، بک‌اسلش) و نشان بده روش بدون quote خطا می‌دهد.

دیدن جواب
Terminal window
touch "my report 2026.txt"
ls "my report 2026.txt"
ls 'my report 2026.txt'
ls my\ report\ 2026.txt
echo "--- بدون quote:"
ls my report 2026.txt 2>&1 | head -3
خروجی
my report 2026.txt
my report 2026.txt
my report 2026.txt
--- بدون quote:
ls: cannot access 'my': No such file or directory
ls: cannot access 'report': No such file or directory
ls: cannot access '2026.txt': No such file or directory
؟ آزمونک
  1. کدام یک پنجره‌ی ورودی/خروجی است و کدام مفسر دستورها؟

  2. در ls -lh /etc، کدام بخش argument است؟

  3. تفاوت echo "$HOME" و echo '$HOME'؟

  4. کد خروج ۰ یعنی…

  5. Ctrl+R برای چیست؟

  6. چرا cd باید builtin باشد؟

  • ترمینال پنجره است؛ shell (مثل bash) دستور را می‌خواند، تجزیه و اجرا می‌کند. zsh شبیه bash با امکانات بیشتر است.
  • هر دستور: command + optionها (-l، --help) + argumentها. shell با فاصله خرد می‌کند؛ برای فاصله در اسم، quote بگذار.
  • "..." متغیر و $(...) را جایگزین می‌کند؛ '...' عیناً نگه می‌دارد.
  • کد خروج در $?: ۰ موفق، غیر صفر خطا.
  • تاریخچه (history، !!، Ctrl+R) و Tab سرعت و دقت را بالا می‌برند؛ رمز را روی خط فرمان نگذار.
  • میانبرها: Ctrl+C قطع، Ctrl+D پایان ورودی، Ctrl+L پاک کردن صفحه، Ctrl+A/E اول/آخر خط.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
echo "متن"چاپ متن
whoamiنام کاربر فعلی
historyفهرست دستورهای قبلی
clear (یا Ctrl+L)پاک کردن صفحه
echo $?کد خروج دستور قبلی
echo $SHELLshell پیش‌فرض
cat /etc/shellsshell های معتبر سیستم
Ctrl+C / Ctrl+Dقطع دستور / پایان ورودی
Ctrl+Rجستجو در تاریخچه
compgen -c prefixدستورهایی که با پیشوند شروع می‌شوند