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

لینک‌ها: hard link و symbolic link

توی این درس یاد می‌گیری فایل در لینوکس دو بخش دارد: اسم (در پوشه) و inode (خود فایل: محتوا و اطلاعاتش). با این دید می‌فهمی hard link (ln) یعنی یک inode با چند اسم و symbolic link (ln -s، «symlink») یعنی یک فایل کوچک که فقط آدرس یک فایل دیگر را دارد. با ls -i و stat inode را می‌بینی، با readlink -f مقصد نهایی یک لینک را پیدا می‌کنی، می‌فهمی لینک شکسته چرا پیش می‌آید و کاربردهای واقعی (مثل sites-enabled در nginx و نسخه‌ی current) را می‌بینی.

گاهی یک فایل باید از چند جا در دسترس باشد: تنظیمات nginx که باید هم در sites-available و هم sites-enabled دیده شود؛ نسخه‌ی فعلی یک برنامه که current نامیده می‌شود ولی هر بار به نسخه‌ی دیگری اشاره می‌کند؛ یا یک فایل بزرگ که نمی‌خواهی کپی‌اش کنی. کپی کردن حجم و هماهنگی می‌خواهد. راه‌حل: لینک.

تشبیه: شماره‌ی پرونده و برچسب روی قفسه

Section titled “تشبیه: شماره‌ی پرونده و برچسب روی قفسه”

inode مثل شماره‌ی پرونده در بایگانی است: محتوا و مشخصات (حجم، مالک، دسترسی‌ها، زمان‌ها) زیر آن شماره است، و خود شماره اسم ندارد. اسم فایل فقط یک برچسب روی قفسه است که می‌گوید «پرونده‌ی شماره‌ی ۱۲۳ اینجاست». یک hard link یعنی برچسب دوم برای همان پرونده. یک symlink یعنی برگه‌ای که روی آن نوشته شده «برو ببین قفسه‌ی …» (آدرس یک برچسب دیگر).

در hard link دو اسم به یک inode می‌رسند (با حذف یکی، دیگری و داده می‌مانند). symlink یک فایل جدا (inode خودش) است که فقط مسیر مقصد را دارد؛ اگر مقصد حذف شود، لینک می‌شکند.

هر فایل یک شماره‌ی inode دارد. ls -i آن را کنار اسم چاپ می‌کند و stat جزئیات بیشتر:

Terminal window
mkdir -p ex1 && cd ex1
echo "محتوای اصلی" > original.txt
ls -i original.txt
echo "--- stat:"
stat -c 'inode=%i تعداد لینک=%h حجم=%s نام=%n' original.txt
خروجی
112341 original.txt
--- stat:
inode=112341 تعداد لینک=1 حجم=22 نام=original.txt

عدد اول inode است و تعداد لینک (%h) برابر ۱: فقط یک اسم به این inode اشاره می‌کند. (دستور stat را بدون flag بزن تا همه‌ی اطلاعات را ببینی.)

Section titled “مثال ۲: hard link، دو اسم، یک فایل”
Terminal window
mkdir -p ex2 && cd ex2
echo "محتوای اصلی" > original.txt
ln original.txt hard.txt
ls -li original.txt hard.txt
echo "--- تعداد لینک:"
stat -c '%n: inode=%i links=%h' original.txt hard.txt
خروجی
113275 -rw-r--r-- 2 ali ali 22 Oct 3 20:04 hard.txt
113275 -rw-r--r-- 2 ali ali 22 Oct 3 20:04 original.txt
--- تعداد لینک:
original.txt: inode=113275 links=2
hard.txt: inode=113275 links=2

هر دو همان inode را دارند و تعداد لینک ۲ شد. دو اسم‌اند برای یک فایل، نه کپی. اثبات: تغییر از یکی، در دیگری دیده می‌شود:

Terminal window
cd ex2
echo "خط اضافه از طریق hard.txt" >> hard.txt
echo "--- original.txt:"
cat original.txt
echo "--- حذف original.txt و خواندن hard.txt:"
rm original.txt
cat hard.txt
stat -c '%n: links=%h' hard.txt
خروجی
--- original.txt:
محتوای اصلی
خط اضافه از طریق hard.txt
--- حذف original.txt و خواندن hard.txt:
محتوای اصلی
خط اضافه از طریق hard.txt
hard.txt: links=1

با حذف اسم original.txt، داده نرفت: hard.txt هنوز هست و تعداد لینک شد ۱. داده فقط وقتی پاک می‌شود که تعداد لینک صفر شود و هیچ برنامه‌ای هم آن را باز نگه نداشته باشد. در واقع rm یعنی «اسم را بردار»، نه «داده را پاک کن».

Section titled “مثال ۳: محدودیت‌های hard link”

hard link فقط در یک فایل‌سیستم و فقط برای فایل‌ها (نه پوشه‌ها) ممکن است:

Terminal window
mkdir -p ex3 && cd ex3
rm -f /tmp/a-hard.txt
echo hi > a.txt
ln a.txt ./a-copy.txt && echo "داخل همان پوشه/دیسک: موفق"
ln /etc/hostname h.txt
mkdir d
ln d d-link
خروجی
داخل همان پوشه/دیسک: موفق
ln: failed to create hard link 'h.txt' => '/etc/hostname': Invalid cross-device link
ln: d: hard link not allowed for directory

دومی (/etc/hostname روی یک mount دیگر است) خطای Invalid cross-device link می‌دهد و سومی hard link not allowed for directory. این دو محدودیت یکی از دلایل وجود symlink است.

⚡ بررسی سریع

بعد از ln a.txt b.txt و rm a.txt، چه می‌شود؟

Section titled “مثال ۴: symlink، یک فایل کوچک با آدرس”

ln -s مقصد نام‌لینک (-s یعنی symbolic). ترتیب مثل cp است: اول «به چه چیزی»، بعد «اسم جدید»:

Terminal window
mkdir -p ex4 && cd ex4
echo "تنظیمات برنامه" > config.yaml
ln -s config.yaml settings.yaml
ls -l config.yaml settings.yaml
echo "--- inode ها:"
ls -i config.yaml settings.yaml
echo "--- محتوا از طریق لینک:"
cat settings.yaml
خروجی
-rw-r--r-- 1 ali ali 28 Oct 3 20:04 config.yaml
lrwxrwxrwx 1 ali ali 11 Oct 3 20:04 settings.yaml -> config.yaml
--- inode ها:
299914 config.yaml
299916 settings.yaml
--- محتوا از طریق لینک:
تنظیمات برنامه

در ls -l اولین حرف l است (link) و بعد از اسم -> config.yaml مقصد را نشان می‌دهد. حجم لینک ۱۱ بایت است (طول متن «config.yaml»؛ یک symlink فقط آدرس را نگه می‌دارد) و inode‌ها متفاوت‌اند: symlink یک فایل جداست. دستورها (cat، nano) لینک را دنبال می‌کنند و به مقصد می‌رسند.

با readlink فقط مقصد را می‌پرسی و readlink -f مسیر نهایی و کامل را (همه‌ی لینک‌ها را باز می‌کند):

Terminal window
cd ex4
ln -s settings.yaml alias.yaml
readlink alias.yaml
readlink -f alias.yaml
خروجی
settings.yaml
/home/ali/ex4/config.yaml

یک زنجیره‌ی دو مرحله‌ای: alias.yaml → settings.yaml → config.yaml؛ -f تا آخر می‌رود.

symlink فقط یک آدرس است؛ اگر مقصد جابه‌جا یا حذف شود، لینک می‌ماند ولی به جایی اشاره می‌کند که نیست:

Terminal window
mkdir -p ex5 && cd ex5
echo "داده" > target.txt
ln -s target.txt mylink
cat mylink
mv target.txt moved.txt
echo "--- بعد از جابه‌جا کردن مقصد:"
ls -l mylink
cat mylink
test -e mylink && echo "لینک سالم" || echo "لینک شکسته است"
خروجی
داده
--- بعد از جابه‌جا کردن مقصد:
lrwxrwxrwx 1 ali ali 10 Oct 3 20:04 mylink -> target.txt
cat: mylink: No such file or directory
لینک شکسته است

ls -l هنوز mylink -> target.txt را نشان می‌دهد، ولی cat می‌گوید فایل نیست؛ در بسیاری از ترمینال‌ها اسم لینک شکسته قرمز می‌شود. test -e (آیا وجود دارد؟) مقصد را دنبال می‌کند و شکسته را تشخیص می‌دهد. راه‌حل: مقصد را برگردان یا لینک را دوباره با آدرس درست بساز. این همان تمرین درس است.

مثال ۶: مسیر نسبی در برابر مطلق

Section titled “مثال ۶: مسیر نسبی در برابر مطلق”

مقصد symlink می‌تواند مطلق (/home/ali/data) یا نسبی باشد. مهم: مقصد نسبی نسبت به پوشه‌ی خودِ لینک حساب می‌شود، نه جایی که تو هستی:

Terminal window
mkdir -p ex6 && cd ex6
mkdir -p proj/conf proj/bin
echo "x=1" > proj/conf/app.conf
cd proj/bin
ln -s ../conf/app.conf app.conf
ls -l app.conf
cat app.conf
echo "--- جابه‌جا کردن لینک به یک پوشه‌ی دیگر:"
mv app.conf ../
cat ../app.conf
خروجی
lrwxrwxrwx 1 ali ali 16 Oct 3 20:04 app.conf -> ../conf/app.conf
x=1
--- جابه‌جا کردن لینک به یک پوشه‌ی دیگر:
cat: ../app.conf: No such file or directory

لینک bin/app.conf با آدرس نسبی ../conf/app.conf کار می‌کرد، ولی وقتی لینک را یک پوشه بالاتر بردیم، همان آدرس از جای جدید به proj/../conf/app.conf (جایی که نیست) اشاره می‌کند و می‌شکند. قاعده: لینک نسبی با جابه‌جا شدن همراه با مقصدش (کل پوشه) سالم می‌ماند؛ لینک مطلق به جای ثابتی اشاره می‌کند ولی با جابه‌جا شدن پروژه می‌شکند.

Section titled “مثال ۷: symlink به پوشه، pwd -P”
Terminal window
mkdir -p ex7/releases/v1 && cd ex7
echo "نسخه ۱" > releases/v1/info.txt
ln -s releases/v1 current
cd current
echo "pwd (منطقی): $(pwd)"
echo "pwd -P (واقعی): $(pwd -P)"
cat info.txt
خروجی
pwd (منطقی): /home/ali/ex7/current
pwd -P (واقعی): /home/ali/ex7/releases/v1
نسخه ۱

وقتی از طریق symlink وارد پوشه می‌شوی، shell مسیر منطقی (با اسم لینک) را نگه می‌دارد و pwd -P (physical) مسیر واقعی را می‌دهد. و cd -P current به‌جای ماندن در لینک، مسیر واقعی را وارد می‌کند.

مثال ۸: کاربرد واقعی، جابه‌جایی نسخه (ln -sfn)

Section titled “مثال ۸: کاربرد واقعی، جابه‌جایی نسخه (ln -sfn)”

همان الگوی سناریو: current را از v1 به v2 ببر، بدون تغییر سرویس:

Terminal window
mkdir -p ex8/releases/v1 ex8/releases/v2 && cd ex8
echo "نسخه ۱" > releases/v1/info.txt
echo "نسخه ۲" > releases/v2/info.txt
ln -s releases/v1 current
echo "الان: $(cat current/info.txt) ← $(readlink current)"
ln -sfn releases/v2 current
echo "بعد از انتشار: $(cat current/info.txt) ← $(readlink current)"
ln -sfn releases/v1 current
echo "rollback: $(cat current/info.txt) ← $(readlink current)"
خروجی
الان: نسخه ۱ ← releases/v1
بعد از انتشار: نسخه ۲ ← releases/v2
rollback: نسخه ۱ ← releases/v1

-s symlink، -f (force: اگر لینک از قبل هست جایگزین کن) و -n (no-dereference: لینک موجود به یک پوشه را مثل فایل ببین، نه اینکه داخل مقصدش لینک بسازد). بدون -n، ln -sf releases/v2 current وقتی current به یک پوشه اشاره می‌کند، لینک جدیدی داخل releases/v1 می‌سازد، اشتباه رایج.

مثال ۹: لینک‌هایی که همین الآن روی سیستم داری

Section titled “مثال ۹: لینک‌هایی که همین الآن روی سیستم داری”

لینوکس پر از symlink است:

Terminal window
ls -l /bin /etc/os-release /usr/bin/python3 /usr/bin/editor 2>&1 | cut -c1-110
echo "--- sites-enabled در nginx:"
ls -l /etc/nginx/sites-enabled/
echo "--- مقصد نهایی python3:"
readlink -f /usr/bin/python3
خروجی
lrwxrwxrwx 1 root root 7 Apr 22 2024 /bin -> usr/bin
lrwxrwxrwx 1 root root 21 Sep 6 14:29 /etc/os-release -> ../usr/lib/os-release
lrwxrwxrwx 1 root root 24 Jun 3 22:50 /usr/bin/editor -> /etc/alternatives/editor
lrwxrwxrwx 1 root root 10 Nov 12 2025 /usr/bin/python3 -> python3.12
--- sites-enabled در nginx:
total 0
lrwxrwxrwx 1 root root 34 Oct 3 09:34 default -> /etc/nginx/sites-available/default
--- مقصد نهایی python3:
/usr/bin/python3.12

/bin به usr/bin اشاره می‌کند (درس ساختار پوشه‌ها)؛ python3 به نسخه‌ی مشخص (python3.12)، تا بتوانی نسخه را عوض کنی؛ و در nginx تنظیم هر سایت در sites-available نوشته می‌شود و برای فعال کردن فقط یک symlink در sites-enabled می‌سازی (و برای غیرفعال کردن، همان لینک را پاک می‌کنی؛ فایل اصلی می‌ماند).

مثال ۱۰: حذف نام و «فایل پاک‌شده‌ی هنوز باز»

Section titled “مثال ۱۰: حذف نام و «فایل پاک‌شده‌ی هنوز باز»”

داده تا وقتی باز باشد نمی‌میرد، حتی بعد از rm. ببین:

Terminal window
mkdir -p ex10 && cd ex10
echo "این داده بعد از rm هم خوانده می‌شود" > ghost.txt
exec 3< ghost.txt # فایل را با شماره‌ی 3 باز نگه دار
rm ghost.txt
ls ghost.txt 2>&1
echo "--- ولی هنوز از طریق fd 3 قابل‌خواندن است:"
cat <&3
exec 3<&- # بستن
خروجی
ls: cannot access 'ghost.txt': No such file or directory
--- ولی هنوز از طریق fd 3 قابل‌خواندن است:
این داده بعد از rm هم خوانده می‌شود

اسم رفت (ls خطا داد) ولی چون یک پروسه (shell ما) فایل را باز نگه داشته بود، inode و محتوا ماندند. همین است که گاهی df می‌گوید دیسک پر است ولی du چیزی پیدا نمی‌کند: فایلی حذف شده ولی هنوز یک پروسه آن را باز دارد (راه‌حل: پروسه را ری‌استارت کن؛ درس منابع سیستم).

پشت پرده: inode و جدول پوشه

Section titled “پشت پرده: inode و جدول پوشه”

فایل‌سیستم‌های لینوکسی (ext4 و…) هر فایل را با یک inode نگه می‌دارند که شامل نوع، دسترسی‌ها، مالک، حجم، زمان‌ها و اشاره‌گر به بلوک‌های داده است؛ اسم فایل در inode نیست. پوشه یک فایل ویژه است که فقط جدولی از «اسم ← شماره‌ی inode» دارد. پس:

  • hard link = یک ردیف دیگر در جدول یک پوشه (شاید همان پوشه یا پوشه‌ی دیگر در همان فایل‌سیستم) که به همان شماره‌ی inode اشاره می‌کند. هسته یک شمارنده‌ی لینک در inode نگه می‌دارد و فقط وقتی به صفر برسد (و باز نباشد)، بلوک‌ها آزاد می‌شوند. چون شماره‌ی inode فقط داخل یک فایل‌سیستم یکتاست، hard link بین فایل‌سیستم‌ها ممکن نیست؛ و برای پوشه‌ها ممنوع است تا حلقه‌ی بی‌پایان (پوشه‌ای که خودش را شامل شود) ساخته نشود.
  • symlink = یک inode با نوع «لینک» که متنی (مسیر مقصد) در خود دارد. هسته هنگام باز کردن مسیر، در صورت دیدن symlink، متن را می‌خواند و مسیر را از نو ادامه می‌دهد؛ برای همین می‌تواند به هر جا (حتی فایل‌سیستم دیگر یا مقصدی که هنوز وجود ندارد) اشاره کند.

حلقه‌ی symlink هم ممکن است (a → b → a)؛ هسته بعد از چند مرحله (حدود ۴۰) خطای Too many levels of symbolic links می‌دهد.

دستور کار
ln مقصد نام hard link
ln -s مقصد نام symbolic link
ln -sfn مقصد نام symlink را (با جایگزینی لینک موجود) بساز
ls -i نمایش inode
ls -l نمایش لینک‌ها با l و ->
stat فایل همه‌ی جزئیات inode و تعداد لینک
readlink لینک مقصد مستقیم
readlink -f لینک مقصد نهایی و کامل
test -e لینک آیا مقصد وجود دارد؟
pwd -P / cd -P مسیر واقعی (بدون لینک)
hard link symlink
inode همان inode inode خودش
حذف مقصد داده می‌ماند لینک می‌شکند
پوشه ممکن نیست ممکن
بین دیسک‌ها ممکن نیست ممکن
تشخیص در ls -l عدد لینک > ۱ حرف l و ->
مصرف هیچ (فقط یک اسم) چند بایت
Terminal window
mkdir -p m1 && cd m1
ln -s link-name actual-target 2>&1
ls -l link-name actual-target 2>&1 | head -3
خروجی
ls: cannot access 'link-name': No such file or directory
lrwxrwxrwx 1 ali ali 9 Oct 3 20:04 actual-target -> link-name

ترتیب: ln -s **مقصد** **نام‌لینک**. اگر اشتباه بنویسی یک لینک شکسته می‌سازی. راه‌حل: به خاطر بسپار «مثل cp: از کجا ← به کجا».

۲) لینک نسبی که به جای اشتباه نسبت داده می‌شود

Section titled “۲) لینک نسبی که به جای اشتباه نسبت داده می‌شود”

مثال ۶. راه‌حل: نسبت به پوشه‌ی خودِ لینک فکر کن، یا از مسیر مطلق استفاده کن.

۳) ln -sf بدون -n روی لینک پوشه

Section titled “۳) ln -sf بدون -n روی لینک پوشه”
Terminal window
mkdir -p m3 && cd m3
mkdir v1 v2
ln -s v1 cur
ln -sf v2 cur
ls -l cur v1 | head -6
خروجی
lrwxrwxrwx 1 ali ali 2 Oct 3 20:04 cur -> v1
v1:
total 0
lrwxrwxrwx 1 ali ali 2 Oct 3 20:04 v2 -> v2

cur به پوشه‌ی v1 اشاره می‌کند و ln -sf v2 cur داخل v1 یک لینک به اسم v2 ساخت (در خروجی v1/ حالا یک v2 دارد) و cur عوض نشد. راه‌حل: ln -sfn.

Section titled “۴) تصور اینکه symlink بکاپ است”

اگر فایل اصلی حذف شود، symlink هیچ داده‌ای ندارد. راه‌حل: برای نسخه‌ی پشتیبان کپی بگیر (cp -a)، نه لینک.

۵) حذف پوشه‌ی لینک با rm -r و / در آخر

Section titled “۵) حذف پوشه‌ی لینک با rm -r و / در آخر”

برای حذف خودِ لینک از rm لینک (بدون -r و بدون / آخر) استفاده کن. با / آخر شل به مقصد می‌رود و ممکن است خطا بگیری یا چیز دیگری را هدف بگیری:

Terminal window
mkdir -p m5 && cd m5
mkdir realdir && touch realdir/file
ln -s realdir dirlink
rm dirlink
ls realdir
خروجی
file

rm dirlink فقط لینک را برداشت و realdir/file سالم ماند.

✎ تمرینآسان

یک فایل notes.txt بساز و یک hard link و یک symlink به آن بساز. با ls -li ببین کدام inode مشترک دارد.

دیدن جواب
Terminal window
mkdir -p t1 && cd t1
echo "یادداشت" > notes.txt
ln notes.txt notes-hard.txt
ln -s notes.txt notes-sym.txt
ls -li notes.txt notes-hard.txt notes-sym.txt
خروجی
342201 -rw-r--r-- 2 ali ali 15 Oct 3 20:04 notes-hard.txt
342241 lrwxrwxrwx 1 ali ali 9 Oct 3 20:04 notes-sym.txt -> notes.txt
342201 -rw-r--r-- 2 ali ali 15 Oct 3 20:04 notes.txt

notes-hard.txt همان inode فایل اصلی را دارد؛ notes-sym.txt inode جدا.

✎ تمرینمتوسط

تمرین اصلی: یک symlink به یک فایل تنظیمات بساز و فایل اصلی را جابه‌جا کن تا لینک بشکند. بعد ثابت کن شکسته است (test -e)، و با ساختن دوباره‌ی لینک به مسیر جدید درستش کن.

دیدن جواب
Terminal window
mkdir -p etc-demo && cd etc-demo
echo "port=80" > app.conf
ln -s app.conf active.conf
cat active.conf
mv app.conf app.conf.old
test -e active.conf && echo "سالم" || echo "شکسته است"
ls -l active.conf
ln -sf app.conf.old active.conf
cat active.conf
test -e active.conf && echo "دوباره سالم"
خروجی
port=80
شکسته است
lrwxrwxrwx 1 ali ali 8 Oct 3 20:04 active.conf -> app.conf
port=80
دوباره سالم
✎ تمرینسخت

یک سناریوی انتشار بنویس: سه نسخه‌ی releases/v1، v2، v3 بساز که هر کدام یک فایل VERSION دارند، یک symlink current، و یک تابع deploy که نسخه را بگیرد و با ln -sfn به آن سوییچ کند و نسخه‌ی فعال را چاپ کند. سه بار صدایش بزن (v2، v3، v1).

دیدن جواب
Terminal window
mkdir -p t3 && cd t3
for v in v1 v2 v3; do mkdir -p releases/$v; echo $v > releases/$v/VERSION; done
ln -s releases/v1 current
deploy() { ln -sfn "releases/$1" current && echo "فعال: $(cat current/VERSION) (لینک → $(readlink current))"; }
deploy v2
deploy v3
deploy v1
خروجی
فعال: v2 (لینک → releases/v2)
فعال: v3 (لینک → releases/v3)
فعال: v1 (لینک → releases/v1)
؟ آزمونک
  1. hard link چیست؟

  2. اگر مقصد یک symlink حذف شود؟

  3. ترتیب درست ساخت symlink؟

  4. کدام یک روی پوشه‌ها و بین فایل‌سیستم‌ها کار می‌کند؟

  5. readlink -f چه می‌کند؟

  6. برای جابه‌جایی نسخه‌ی current بین v1 و v2؟

  • فایل = inode (محتوا و مشخصات) + اسم (ردیفی در جدول یک پوشه). ls -i و stat inode را نشان می‌دهند.
  • hard link (ln a b): اسم دوم برای همان inode؛ حذف یکی، دیگری سالم؛ فقط فایل و فقط داخل یک فایل‌سیستم.
  • symlink (ln -s مقصد نام): فایل کوچکی با آدرس مقصد؛ روی پوشه و بین دیسک‌ها کار می‌کند؛ اگر مقصد برود می‌شکند.
  • لینک نسبی از پوشه‌ی خودِ لینک حساب می‌شود؛ readlink -f مقصد نهایی؛ ln -sfn برای سوییچ امن لینک پوشه‌ها (current).
  • rm یعنی «اسم را بردار»؛ داده تا وقتی لینک یا پروسه‌ی بازکننده باشد می‌ماند.
  • نمونه‌های واقعی: /bin → usr/bin، python3 → python3.12، nginx sites-enabled، نسخه‌ی current.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
ln original hardhard link
ln -s target linksymbolic link
ln -sfn releases/v2 currentسوییچ لینک پوشه (انتشار)
ls -liinode و لینک‌ها
stat -c "%i %h %n" finode و تعداد لینک
readlink linkمقصد مستقیم
readlink -f linkمقصد نهایی کامل
test -e linkلینک سالم است؟
pwd -Pمسیر واقعی (بدون symlink)