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

متغیرهای محیطی و تنظیمات shell

توی این درس یاد می‌گیری shell برای هر برنامه‌ای که اجرا می‌کند یک «کارت اطلاعات» همراهش می‌دهد: متغیرهای محیطی (environment variables). با env و export VAR=value آن‌ها را می‌بینی و می‌سازی؛ می‌فهمی PATH چطور تعیین می‌کند دستور ls کجا پیدا شود؛ فرق .bashrc و .profile را می‌دانی و تنظیماتت را دائمی می‌کنی؛ با alias میان‌بر می‌سازی و با source ~/.bashrc بدون باز کردن ترمینال جدید اعمالش می‌کنی. تمرین: یک پوشه‌ی اسکریپت شخصی به PATH اضافه می‌کنی.

مسئله: برنامه‌ها چطور تنظیمات را از shell می‌گیرند؟

Section titled “مسئله: برنامه‌ها چطور تنظیمات را از shell می‌گیرند؟”

وقتی ls را می‌زنی، shell از کجا می‌داند آن برنامه کجاست؟ وقتی git commit ویرایشگر باز می‌کند، از کجا می‌داند چه ویرایشگری؟ وقتی یک برنامه باید بداند «پوشه‌ی خانه‌ی کاربر کجاست»، «زبان سیستم چیست» یا «رمز دیتابیس چیست»، از کجا بخواند؟ جواب هر سه یکی است: متغیرهای محیطی؛ جفت‌های نام=مقدار که هر پروسه از والدش به ارث می‌برد.

تشبیه: کارت اطلاعاتی که به هر کارمند جدید می‌دهند

Section titled “تشبیه: کارت اطلاعاتی که به هر کارمند جدید می‌دهند”

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

متغیرهای export‌شده به هر پروسه‌ی فرزند به‌صورت کپی می‌رسند و بعد مسیر برگشتی ندارند: تغییر در فرزند والد را عوض نمی‌کند.

مثال ۱: متغیر shell در برابر متغیر محیطی

Section titled “مثال ۱: متغیر shell در برابر متغیر محیطی”

یک متغیر ساده در shell با نام=مقدار ساخته می‌شود (بدون فاصله دور =) و با $نام خوانده می‌شود. اما فقط خود shell آن را دارد؛ برنامه‌های فرزند نه، مگر export شود:

Terminal window
NAME=Ali
echo "در shell: $NAME"
echo "--- فرزند (یک bash جدید) آن را می‌بیند؟"
bash -c 'echo "فرزند: [$NAME]"'
echo "--- بعد از export:"
export NAME
bash -c 'echo "فرزند: [$NAME]"'
echo "--- printenv و env هم آن را نشان می‌دهند:"
printenv NAME
env | grep '^NAME='
echo "--- unset:"
unset NAME
echo "بعد از unset: [$NAME]"
خروجی
در shell: Ali
--- فرزند (یک bash جدید) آن را می‌بیند؟
فرزند: []
--- بعد از export:
فرزند: [Ali]
--- printenv و env هم آن را نشان می‌دهند:
Ali
NAME=Ali
--- unset:
بعد از unset: []

دو شکل export: export VAR=value (ساختن و export یکجا؛ مثل export NAME=Ali)، یا اول NAME=Ali و بعد export NAME. و قراردادی که همه‌ی دنیا رعایت می‌کنند: نام متغیرهای محیطی حروف بزرگ و _ است (DB_HOST، EDITOR).

مثال ۲: env، متغیرهای مهم و محیط یک دستور

Section titled “مثال ۲: env، متغیرهای مهم و محیط یک دستور”

env (یا printenv) همه‌ی متغیرهای محیطی را چاپ می‌کند. چند تای مهم:

Terminal window
echo "--- چند متغیر استاندارد:"
env | grep -E '^(HOME|USER|SHELL|LANG|PWD)='
echo "--- تعداد کل متغیرهای محیط این shell: $(env | wc -l)"
echo "--- مقدار یک متغیر با echo (علامت \$ و نام):"
echo "پوشه‌ی خانه‌ی من: $HOME | کاربر: $USER"
خروجی
--- چند متغیر استاندارد:
SHELL=/bin/bash
PWD=/home/ali
HOME=/home/ali
LANG=C.UTF-8
USER=ali
--- تعداد کل متغیرهای محیط این shell: 13
--- مقدار یک متغیر با echo (علامت $ و نام):
پوشه‌ی خانه‌ی من: /home/ali | کاربر: ali
متغیر معنی
HOME پوشه‌ی خانه‌ی کاربر (همان ~)
USER نام کاربر
SHELL shell پیش‌فرض
PWD پوشه‌ی فعلی
LANG زبان و رمزگذاری سیستم
PATH فهرست پوشه‌هایی که دستورها در آن‌ها جستجو می‌شوند (مثال ۳)
EDITOR ویرایشگر پیش‌فرض برنامه‌هایی مثل git
TZ منطقه‌ی زمانی

یک متغیر را می‌شود فقط برای یک دستور تنظیم کرد: VAR=value دستور. محیط shell عوض نمی‌شود. مثلاً منطقه‌ی زمانی فقط برای یک date:

Terminal window
echo "--- ساعت سیستم:"
date
echo "--- فقط برای همین یک دستور، به وقت تهران:"
TZ=Asia/Tehran date
echo "--- و بعدش دوباره عادی (TZ تغییری در shell نداد):"
date
echo "TZ در shell: [$TZ]"
خروجی
--- ساعت سیستم:
Sat Oct 3 21:24:01 UTC 2026
--- فقط برای همین یک دستور، به وقت تهران:
Sun Oct 4 00:54:01 +0330 2026
--- و بعدش دوباره عادی (TZ تغییری در shell نداد):
Sat Oct 3 21:24:01 UTC 2026
TZ در shell: []

مثال ۳: PATH، دستور از کجا پیدا می‌شود؟

Section titled “مثال ۳: PATH، دستور از کجا پیدا می‌شود؟”

وقتی ls را می‌زنی، shell فایلی به این نام را به ترتیب در هر پوشه‌ی PATH (جداشده با :) می‌گردد و اولین مورد را اجرا می‌کند. مقدارش را با echo $PATH می‌بینی:

Terminal window
echo "$PATH"
echo "--- هر پوشه در یک خط:"
echo "$PATH" | tr ':' '\n'
echo "--- دستور ls از کدام پوشه می‌آید؟"
which ls
type -a ls
echo "--- یک دستور نصب‌نشده:"
mytool
خروجی
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
--- هر پوشه در یک خط:
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
--- دستور ls از کدام پوشه می‌آید؟
/usr/bin/ls
ls is /usr/bin/ls
ls is /bin/ls
--- یک دستور نصب‌نشده:
bash: mytool: command not found

which اولین مسیر منطبق را می‌دهد و type -a همه‌ی آن‌ها را (در Ubuntu جدید /bin لینک به /usr/bin است و هر دو مسیر یک فایل‌اند). mytool که در هیچ پوشه‌ی PATH نیست، command not found می‌دهد. حالا تمرین اصلی درس: یک پوشه‌ی اسکریپت شخصی بساز و به PATH اضافه کن:

Terminal window
mkdir -p ~/bin
printf '#!/bin/sh\necho "سلام از اسکریپت شخصی من"\n' > ~/bin/hello
chmod +x ~/bin/hello
echo "--- قبل از اضافه‌کردن به PATH:"
hello
echo "--- با مسیر کامل کار می‌کند:"
~/bin/hello
echo "--- بعد از export PATH=\"\$HOME/bin:\$PATH\":"
export PATH="$HOME/bin:$PATH"
hello
command -v hello
echo "$PATH" | tr ':' '\n' | head -3
خروجی
--- قبل از اضافه‌کردن به PATH:
bash: hello: command not found
--- با مسیر کامل کار می‌کند:
سلام از اسکریپت شخصی من
--- بعد از export PATH="$HOME/bin:$PATH":
سلام از اسکریپت شخصی من
/home/ali/bin/hello
/home/ali/bin
/usr/local/sbin
/usr/local/bin

export PATH="$HOME/bin:$PATH" یعنی «~/bin را به ابتدای فهرست قبلی اضافه کن». مقدار قبلی PATH را حتماً نگه دار (:$PATH آخرش) وگرنه بقیه‌ی دستورها گم می‌شوند (اشتباهات رایج). ترتیب مهم است: اولین پوشه‌ی منطبق برنده است، پس برنامه‌ی تو می‌تواند دستور سیستمی را پنهان کند:

Terminal window
printf '#!/bin/sh\necho "من ls قلابی‌ام!"\n' > ~/bin/ls
chmod +x ~/bin/ls
ls
type -a ls
rm ~/bin/ls
hash -r
echo "--- بعد از حذفش:"
type -a ls | head -1
خروجی
من ls قلابی‌ام!
ls is /home/ali/bin/ls
ls is /usr/bin/ls
ls is /bin/ls
--- بعد از حذفش:
ls is /usr/bin/ls

(hash -r حافظه‌ی shell از مسیر دستورها را پاک می‌کند؛ shell مسیر اولین پیدا‌شدن را در یک جدول نگه می‌دارد تا هر بار نگردد.) به همین دلیل پوشه‌ی خودت را اول می‌گذاری فقط وقتی عمداً می‌خواهی دستور سیستمی را عوض کنی؛ در غیر این صورت می‌توانی آخر هم بگذاری (PATH="$PATH:$HOME/bin").

مثال ۴: .profile و .bashrc، تنظیمات دائمی

Section titled “مثال ۴: .profile و .bashrc، تنظیمات دائمی”

متغیری که با export در یک ترمینال ساختی با بسته‌شدن آن ترمینال می‌رود. برای دائمی‌شدن، آن را در یکی از فایل‌های شروع (startup files) shell می‌نویسی که هنگام اجرا خوانده می‌شوند:

.profile فقط برای shell ورود (login: وقتی با ssh یا کنسول وارد می‌شوی) یک بار خوانده می‌شود؛ .bashrc برای هر shell تعاملی تازه (هر پنجره‌ی ترمینال). در Ubuntu، .profile خودش .bashrc را هم صدا می‌زند.

فایل‌های پیش‌فرض Ubuntu را ببین. در ~/.profile یک بلوک هست که ~/bin را خودکار به PATH اضافه می‌کند اگر وجود داشته باشد (برای همین روی ssh بعدی تو، ~/bin خودش اضافه می‌شود):

Terminal window
echo "--- ~/.profile (بدون توضیح و خط خالی):"
grep -v '^#' ~/.profile | grep -v '^$'
echo "--- ~/.bashrc: خط‌های اول (اگر shell تعاملی نباشد همان‌جا خارج می‌شود):"
grep -v '^#' ~/.bashrc | grep -v '^$' | head -6
خروجی
--- ~/.profile (بدون توضیح و خط خالی):
if [ -n "$BASH_VERSION" ]; then
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
if [ -d "$HOME/.local/bin" ] ; then
PATH="$HOME/.local/bin:$PATH"
fi
--- ~/.bashrc: خط‌های اول (اگر shell تعاملی نباشد همان‌جا خارج می‌شود):
case $- in
*i*) ;;
*) return;;
esac
HISTCONTROL=ignoreboth
shopt -s histappend

حالا ثابت می‌کنم کدام فایل چه وقت خوانده می‌شود: در هر دو یک خط نشانه (marker) اضافه می‌کنم و چهار نوع shell را اجرا می‌کنم (-l یعنی login، -i یعنی تعاملی):

Terminal window
echo 'echo " [.bashrc خوانده شد]"' >> ~/.bashrc
echo 'echo " [.profile خوانده شد]"' >> ~/.profile
echo "--- bash -c (نه login، نه تعاملی؛ مثل اجرای یک اسکریپت):"
bash -c 'true'
echo "--- bash -ic (تعاملی، مثل باز کردن یک پنجره‌ی ترمینال):"
bash -ic 'true' 2>/dev/null
echo "--- bash -lc (login ولی غیرتعاملی):"
bash -lc 'true'
echo "--- bash -lic (login و تعاملی، مثل ورود با ssh):"
bash -lic 'true' 2>/dev/null
echo "--- نشانه‌ها را برمی‌دارم:"
sed -i '/خوانده شد/d' ~/.bashrc ~/.profile
bash -lic 'true' 2>/dev/null
echo "(دیگر چیزی چاپ نمی‌شود)"
خروجی
--- bash -c (نه login، نه تعاملی؛ مثل اجرای یک اسکریپت):
--- bash -ic (تعاملی، مثل باز کردن یک پنجره‌ی ترمینال):
[.bashrc خوانده شد]
--- bash -lc (login ولی غیرتعاملی):
[.profile خوانده شد]
--- bash -lic (login و تعاملی، مثل ورود با ssh):
[.bashrc خوانده شد]
[.profile خوانده شد]
--- نشانه‌ها را برمی‌دارم:
(دیگر چیزی چاپ نمی‌شود)

اسکریپت‌ها (bash -c) هیچ‌کدام را نمی‌خوانند، پنجره‌ی ترمینال فقط .bashrc را، و ورود با ssh هر دو را (چون .profile در انتهایش .bashrc را هم صدا می‌زند). قاعده‌ی کاربردی: export و PATH را در ~/.profile (یا ~/.bashrc) بگذار و alias و تابع را در ~/.bashrc. (شخصی‌سازی همه‌ی کاربرهای سیستم: /etc/environment و /etc/profile.)

alias یک میان‌بر است: alias ll='ls -la' یعنی «هر وقت ll زدم، ls -la اجرا کن». چون alias و .bashrc به shell تعاملی مربوط‌اند، یک ترمینال مجازی واقعی با .bashrc پیش‌فرض باز می‌کنم (مثل درس‌های قبل با tmux؛ علامت ترمینال ali@pc:~$ است):

Terminal window
run_in_tmux() { tmux -u kill-server 2>/dev/null; tmux -u new-session -d -s t -x "${TW:-76}" -y "${TH:-24}" "$@"; sleep 1; }
screen() { tmux capture-pane -t t -p | sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}'; }
TH=22
run_in_tmux "bash"
tmux send-keys -t t "alias ll='ls -la'" Enter; sleep 1
tmux send-keys -t t "ll ~/bin" Enter; sleep 1
tmux send-keys -t t "alias | grep -E '^alias (ll|ls)='" Enter; sleep 1
tmux send-keys -t t "type ll" Enter; sleep 1
tmux send-keys -t t "unalias ll" Enter; sleep 1
tmux send-keys -t t "ll" Enter; sleep 1
screen
خروجی
ali@pc:~$ alias ll='ls -la'
ali@pc:~$ ll ~/bin
total 16
drwxr-xr-x 2 ali ali 4096 Oct 3 21:24 .
drwxr-xr-x 1 ali ali 4096 Oct 3 21:24 ..
-rwxr-xr-x 1 ali ali 60 Oct 3 21:24 hello
ali@pc:~$ alias | grep -E '^alias (ll|ls)='
alias ll='ls -la'
alias ls='ls --color=auto'
ali@pc:~$ type ll
ll is aliased to `ls -la'
ali@pc:~$ unalias ll
ali@pc:~$ ll
bash: ll: command not found
ali@pc:~$

alias بدون آرگومان همه‌ی alias های فعلی را نشان می‌دهد (اینجا با grep فقط دو تا را گرفتم؛ .bashrc پیش‌فرض Ubuntu خودش چند تا ساخته، مثل ls رنگی؛ و ll را من دوباره تعریف کردم)، type ll می‌گوید ll یک alias است، و unalias ll برش می‌دارد (بعدش ll دیگر نیست). مشکل: alias فقط در همین shell تا بسته‌شدن آن زنده است. دائمی‌اش را در ~/.bashrc می‌نویسی. یک نکته‌ی اجرایی: تغییر .bashrc روی ترمینال فعلی اثر نمی‌گذارد، چون فقط هنگام شروع shell خوانده می‌شود؛ باید source بزنی (یا ترمینال تازه باز کنی):

Terminal window
tmux send-keys -t t "clear" Enter
tmux send-keys -t t "echo \"alias hi='echo سلام از alias'\" >> ~/.bashrc" Enter; sleep 1
tmux send-keys -t t "hi" Enter; sleep 1
tmux send-keys -t t "source ~/.bashrc" Enter; sleep 1
tmux send-keys -t t "hi" Enter; sleep 1
screen
tmux -u kill-server
خروجی
ali@pc:~$ echo "alias hi='echo سلام از alias'" >> ~/.bashrc
ali@pc:~$ hi
bash: hi: command not found
ali@pc:~$ source ~/.bashrc
ali@pc:~$ hi
سلام از alias
ali@pc:~$

قبل از source مجهول بود (hi: command not found) و بعدش کار کرد. source فایل (همان . فایل) فایل را داخل همین shell اجرا می‌کند. alias ها در اسکریپت‌ها هم کار نمی‌کنند:

Terminal window
alias gs='echo من alias هستم'
gs
echo "--- راه‌حل برای کار تکرارشونده در اسکریپت: تابع"
greet() { echo "سلام، $1!"; }
greet Ali
خروجی
bash: gs: command not found
--- راه‌حل برای کار تکرارشونده در اسکریپت: تابع
سلام، Ali!

(در shell غیرتعاملی، alias ها بسط داده نمی‌شوند: command not found. برای کار پیچیده‌تر از جایگزینی ساده، یک تابع در .bashrc بنویس؛ تابع آرگومان می‌گیرد: mkcd() { mkdir -p "$1" && cd "$1"; }.)

⚡ بررسی سریع

تغییری در ~/.bashrc داده‌ای. چطور آن را روی ترمینال فعلی اعمال می‌کنی بدون بستنش؟

مثال ۶: source در برابر اجرای اسکریپت

Section titled “مثال ۶: source در برابر اجرای اسکریپت”

فرق source با اجرای عادی را با یک فایل تنظیمات ساده ببین (همان الگوی فایل‌های .env):

Terminal window
printf 'GREETING=salam\nexport MODE=dev\n' > app.env
echo "--- اجرا مثل برنامه (فرزند):"
bash app.env
echo "GREETING در shell من: [$GREETING]"
echo "--- source (داخل همین shell):"
source app.env
echo "GREETING در shell من: [$GREETING] MODE: [$MODE]"
echo "--- فرزند فقط MODE را (که export شده) می‌بیند:"
bash -c 'echo "فرزند: GREETING=[$GREETING] MODE=[$MODE]"'
خروجی
--- اجرا مثل برنامه (فرزند):
GREETING در shell من: []
--- source (داخل همین shell):
GREETING در shell من: [salam] MODE: [dev]
--- فرزند فقط MODE را (که export شده) می‌بیند:
فرزند: GREETING=[] MODE=[dev]

اجرای عادی در پروسه‌ی فرزند است و متغیرهایش با پایانش می‌روند، پس GREETING در shell من نماند. source در همان shell اجرا می‌کند، پس هر دو متغیر ماندند، ولی فقط MODE که export شده به فرزند می‌رسد.

مثال ۷: رازها، متغیر محیطی نه خط فرمان

Section titled “مثال ۷: رازها، متغیر محیطی نه خط فرمان”

رمز و توکن را روی خط فرمان نده؛ خط فرمان همه‌ی پروسه‌ها را هر کاربری با ps می‌بیند. محیط پروسه را فقط صاحب پروسه (و root) می‌خواند. دو پروسه با یک رمز نمایشی راه می‌اندازم و از دید کاربر دیگری (nobody) نگاه می‌کنم:

Terminal window
python3 -c 'import time; time.sleep(60)' --password=RAZ-KHATT &
p1=$!
DB_PASSWORD=RAZ-MOHIT python3 -c 'import time; time.sleep(60)' &
p2=$!
sleep 1
echo "--- کاربر دیگر (nobody) با ps چه می‌بیند:"
sudo -u nobody ps -o args= -p $p1,$p2
echo "--- و می‌تواند محیط پروسه‌ی دوم را بخواند؟"
sudo -u nobody cat /proc/$p2/environ
echo "--- خود صاحب می‌تواند:"
tr '\0' '\n' < /proc/$p2/environ | grep '^DB_PASSWORD='
kill $p1 $p2
خروجی
--- کاربر دیگر (nobody) با ps چه می‌بیند:
python3 -c import time; time.sleep(60) --password=RAZ-KHATT
python3 -c import time; time.sleep(60)
--- و می‌تواند محیط پروسه‌ی دوم را بخواند؟
cat: /proc/1210/environ: Permission denied
--- خود صاحب می‌تواند:
DB_PASSWORD=RAZ-MOHIT

رمز روی خط فرمان (--password=RAZ-KHATT) برای هر کاربری با ps پیداست، ولی محیط (DB_PASSWORD) نه: nobody با Permission denied رد شد. (این رمزها نمایشی‌اند. در دنیای واقعی رمز را در یک فایل با دسترسی 600 یا در متغیری که از آن فایل خوانده می‌شود نگه دار، و در ~/.bash_history و git نگذارش.) به همین دلیل برنامه‌های مدرن تنظیمات را از محیط می‌خوانند (در درس‌های داکر هم docker run -e همین است).

پشت پرده: متغیرها کجا زندگی می‌کنند؟

Section titled “پشت پرده: متغیرها کجا زندگی می‌کنند؟”

محیط یک پروسه فقط رشته‌هایی نام=مقدار است که هسته هنگام exec در حافظه‌ی آن می‌گذارد. وقتی shell برنامه‌ای را اجرا می‌کند، همه‌ی متغیرهای export‌شده را به‌عنوان کپی به آن می‌دهد (شکل بالا). هسته آن‌ها را در /proc/PID/environ نمایش می‌دهد (جداشده با کاراکتر NUL):

Terminal window
NAME=Ali bash -c 'echo "--- محیط این پروسه (فقط چند خط، NUL را با خط جدید عوض کردم):"; tr "\0" "\n" < /proc/$$/environ | grep -E "^(NAME|HOME)="'
echo "--- فرزند متغیر را عوض می‌کند؛ والد تغییر نمی‌کند:"
export COLOR=blue
bash -c 'COLOR=red; echo "فرزند: $COLOR"'
echo "والد: $COLOR"
خروجی
--- محیط این پروسه (فقط چند خط، NUL را با خط جدید عوض کردم):
NAME=Ali
HOME=/home/ali
--- فرزند متغیر را عوض می‌کند؛ والد تغییر نمی‌کند:
فرزند: red
والد: blue

متغیرهای shell (که export نشده‌اند) فقط در حافظه‌ی خود shell هستند و در /proc/PID/environ نمی‌آیند. و env را وقتی بدون آرگومان بزنی همین فهرست را چاپ می‌کند، و env VAR=value دستور همان VAR=value دستور است (برای وقتی که shell غیر bash باشد مفید است).

دستور کار
NAME=value متغیر shell (فقط همین shell)
export NAME=value / export NAME متغیر محیطی (برای فرزندها)
echo "$NAME" مقدار (همیشه داخل گیومه)
printenv NAME / env نمایش محیط
unset NAME حذف
VAR=value دستور فقط برای همین یک دستور
echo "$PATH" | tr ':' '\n' هر پوشه‌ی PATH در یک خط
export PATH="$HOME/bin:$PATH" افزودن پوشه به ابتدای PATH
which نام / type -a نام کدام فایل اجرا می‌شود / همه‌ی منطبق‌ها
hash -r پاک‌کردن حافظه‌ی مسیر دستورها
alias ll='ls -la' / unalias ll میان‌بر
source ~/.bashrc اعمال تغییر فایل شروع در همین shell
فایل چه وقت چه چیزی بنویس
~/.profile shell ورود (login، مثل ssh) export و PATH
~/.bashrc هر shell تعاملی alias، تابع، پرامپت
/etc/environment ورود همه‌ی کاربرها متغیرهای سراسری ساده (نام=مقدار)
Terminal window
export GREETING = salam
NAME = Ali
echo "درست: بدون فاصله"
NAME=Ali; echo "NAME=$NAME"
خروجی
bash: export: `=': not a valid identifier
bash: NAME: command not found
درست: بدون فاصله
NAME=Ali

در نام=مقدار فاصله نباید باشد: با فاصله، shell NAME را یک دستور می‌بیند و = و Ali را آرگومان‌هایش. راه‌حل: NAME=Ali.

۲) PATH را بازنویسی کردن، نه اضافه‌کردن

Section titled “۲) PATH را بازنویسی کردن، نه اضافه‌کردن”
Terminal window
echo "--- فقط ~/bin در PATH، ls گم می‌شود:"
( PATH=$HOME/bin; ls )
echo "--- درست: PATH قبلی را نگه دار"
( PATH="$HOME/bin:$PATH"; ls ~/bin )
خروجی
--- فقط ~/bin در PATH، ls گم می‌شود:
bash: ls: command not found
--- درست: PATH قبلی را نگه دار
hello

وقتی PATH=/opt/bin (بدون $PATH) بنویسی، همه‌ی پوشه‌های قبلی (/usr/bin…) حذف می‌شوند و حتی ls پیدا نمی‌شود. (این‌جا داخل ( ... ) یک shell فرعی بود تا shell اصلی خراب نشود؛ اگر در .bashrc این اشتباه را بکنی، هر ترمینال تازه خراب شروع می‌شود و باید با /usr/bin/nano ~/.bashrc و مسیر کامل درستش کنی.) راه‌حل: همیشه PATH="…:$PATH" یا PATH="$PATH:…".

برنامه‌ی فرزند متغیر را نمی‌بیند (مثال ۱). علامتش: «در shell خودم echo $VAR درست است ولی برنامه می‌گوید تنظیم نشده». راه‌حل: export VAR.

۴) متغیر بدون گیومه با فاصله یا *

Section titled “۴) متغیر بدون گیومه با فاصله یا *”
Terminal window
mkdir -p ex4 && cd ex4
FILE="my notes.txt"
echo "--- بدون گیومه: دو آرگومان!"
touch $FILE
ls
rm -f my notes.txt
echo "--- با گیومه: یک آرگومان"
touch "$FILE"
ls
cd .. && rm -rf ex4
خروجی
--- بدون گیومه: دو آرگومان!
my
notes.txt
--- با گیومه: یک آرگومان
my notes.txt

shell متغیر بدون گیومه را بعد از جایگزینی به کلمه‌ها می‌شکند؛ touch $FILE دو فایل my و notes.txt ساخت. راه‌حل: همیشه "$FILE".

۵) انتظار اعمال فوری تغییر .bashrc

Section titled “۵) انتظار اعمال فوری تغییر .bashrc”

مثال ۵: تغییر فایل روی ترمینال باز تأثیری ندارد تا source یا ترمینال تازه. راه‌حل: source ~/.bashrc.

مثال ۵: gs: command not found. راه‌حل: تابع یا اسکریپت مستقل در ~/bin.

✎ تمرینآسان

مقدار HOME، USER و SHELL را با echo چاپ کن و با printenv هم یکی را بخوان.

دیدن جواب
Terminal window
echo "HOME=$HOME USER=$USER SHELL=$SHELL"
printenv HOME
خروجی
HOME=/home/ali USER=ali SHELL=/bin/bash
/home/ali
✎ تمرینمتوسط

تمرین اصلی: یک پوشه‌ی اسکریپت شخصی به PATH اضافه کن، دائمی. پوشه‌ی ~/tools را بساز، اسکریپت now (که تاریخ و ساعت را چاپ می‌کند) را در آن بگذار، خط export PATH را در ~/.bashrc بنویس، و با یک shell تعاملی تازه (bash -ic) ثابت کن now بدون مسیر کامل کار می‌کند.

دیدن جواب
Terminal window
mkdir -p ~/tools
printf '#!/bin/sh\ndate "+امروز %%F ساعت %%T"\n' > ~/tools/now
chmod +x ~/tools/now
echo 'export PATH="$HOME/tools:$PATH"' >> ~/.bashrc
echo "--- shell فعلی هنوز نمی‌داند:"
now
echo "--- shell تعاملی تازه (مثل باز کردن ترمینال جدید):"
bash -ic 'now; command -v now' 2>/dev/null
خروجی
--- shell فعلی هنوز نمی‌داند:
bash: now: command not found
--- shell تعاملی تازه (مثل باز کردن ترمینال جدید):
امروز 2026-10-03 ساعت 21:24:14
/home/ali/tools/now
✎ تمرینسخت

در ~/.bashrc یک alias (ll) و یک تابع (mkcd: پوشه بساز و واردش شو) و یک متغیر محیطی (EDITOR=nano) بنویس و در یک shell تعاملی تازه نشان بده هر سه کار می‌کنند.

دیدن جواب
Terminal window
cat >> ~/.bashrc <<'EOF'
alias ll='ls -la'
mkcd() { mkdir -p "$1" && cd "$1"; }
export EDITOR=nano
EOF
bash -ic 'type ll; mkcd /tmp/lx-new-dir && pwd; echo "EDITOR=$EDITOR"; cd /; rmdir /tmp/lx-new-dir' 2>/dev/null
خروجی
ll is aliased to `ls -la'
/tmp/lx-new-dir
EDITOR=nano
؟ آزمونک
  1. فرق NAME=Ali و export NAME=Ali؟

  2. PATH چه کاری می‌کند؟

  3. درست‌ترین راه برای افزودن ~/bin به PATH؟

  4. کدام فایل برای هر پنجره‌ی ترمینال تازه (shell تعاملی) خوانده می‌شود و جای مناسب alias است؟

  5. تغییری در ~/.bashrc داده‌ای. چطور بدون ترمینال جدید اعمالش می‌کنی؟

  6. چرا رمز را به‌جای خط فرمان در متغیر محیطی می‌دهیم؟

  • متغیر محیطی: جفت‌های نام=مقدار که پروسه به فرزندانش می‌دهد (کپی). NAME=v فقط shell، export NAME=v برای فرزندها. env/printenv فهرست می‌کنند و VAR=v دستور فقط برای یک دستور است. بدون فاصله دور =، و "$NAME" همیشه داخل گیومه.
  • PATH: پوشه‌های جستجوی دستور (با :)، اولین منطبق برنده. export PATH="$HOME/bin:$PATH" و type -a برای دیدن همه‌ی منطبق‌ها.
  • ماندگاری: ~/.profile (shell ورود) و ~/.bashrc (هر shell تعاملی)؛ source ~/.bashrc اعمال فوری.
  • alias برای میان‌بر در shell تعاملی؛ برای کار بیشتر تابع در .bashrc (alias در اسکریپت کار نمی‌کند).
  • source در همین shell اجرا می‌کند؛ اجرای عادی اسکریپت یک فرزند است.
  • رازها: در محیط یا فایل با دسترسی ۶۰۰، نه روی خط فرمان (که با ps پیداست).
برگه‌ی تقلب این درس
دستورکاری که می‌کند
NAME=value / export NAME=valueمتغیر shell / محیطی
echo "$NAME" / printenv NAME / envخواندن و فهرست
unset NAMEحذف
VAR=value دستورفقط برای یک دستور (مثلاً TZ=Asia/Tehran date)
echo "$PATH" | tr ":" "\n"پوشه‌های PATH
export PATH="$HOME/bin:$PATH"افزودن پوشه به PATH
which نام / type -a نامکدام فایل اجرا می‌شود
alias ll='ls -la' / unalias llمیان‌بر
source ~/.bashrcاعمال تغییر بدون ترمینال جدید
bash -ic 'دستور'آزمایش در یک shell تعاملی تازه
~/.profile / ~/.bashrcماندگاری: shell ورود / هر ترمینال