Files, Directories and Links
Two objectives that belong together, because the difference between a hard link and a soft link is invisible until you move or delete what they point at. Both are broken here on purpose, and the two things a hard link cannot do are demonstrated rather than listed.
Essential Tools Guide 7 of 67 Beginner
- OSRHEL 10.0 (Coughlan)
- Kernel6.12.0-55.9.1.el10_0
- dnf4.20.0
- Flatpak1.16.0
- TimeAbout 14 min
- Reviewed23 August 2026
Written against the versions above. Nothing here is RHEL-specific - it is filesystem behaviour. The one detail that varies is that a hard link cannot cross a filesystem boundary, and on this machine `/boot` is a separate filesystem, which is what makes that demonstrable.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| RHCSA-A01 | 192.168.0.31 | RHEL 10.0 (Coughlan) | Practice node (graded) - spare /dev/sda | 2 Core | 4 GB | 50 GB + 15 GB |
Before you start
- A shell.
- The session builds and removes
~/fl.
-
Creating, copying, moving, removing
mkdir -p ~/fl/one/two/three-pcreates the whole chain and does not complain if it already exists - which is why it is the form to use in scripts.cpneeds-rfor a directory, andmvneeds nothing special because it is renaming rather than copying. The one that surprises people:rmdir: failed to remove 'one-copy': Directory not empty exit=1 - rmdir only removes EMPTY directoriesrmdiris not a weakerrm -r- it refuses to remove anything containing files, which makes it the safe choice when you believe a directory is already empty.rm -rremoves it and everything inside without asking.mvbetween two names on the same filesystem does not move data at all; it rewrites a directory entry. That is why renaming a 40 GB file is instant and copying it is not.bash Example session mkdir -p ~/fl/one/two/three && ls -R ~/fl | head -10/home/sysadmin/fl:one /home/sysadmin/fl/one:two /home/sysadmin/fl/one/two:three /home/sysadmin/fl/one/two/three:cd ~/fl && touch a.txt b.txt && echo "content of a" > a.txt && ls -ltotal 4-rw-r--r--. 1 sysadmin sysadmin 13 Aug 23 10:55 a.txt-rw-r--r--. 1 sysadmin sysadmin 0 Aug 23 10:55 b.txtdrwxr-xr-x. 3 sysadmin sysadmin 17 Aug 23 10:55 onecd ~/fl && cp a.txt copy.txt && cp -r one one-copy && ls -d one* && cat copy.txtoneone-copycontent of acd ~/fl && mv b.txt renamed.txt && ls; rmdir one-copy 2>&1; echo "exit=$? - rmdir only removes EMPTY directories"a.txtcopy.txtoneone-copyrenamed.txtrmdir: failed to remove 'one-copy': Directory not emptyexit=1 - rmdir only removes EMPTY directoriesExpected resultA directory tree, a copy, a rename, and rmdir refusing.
Success conditionYou know which delete command protects you and which does not.
-
A hard link is the same file
262184 -rw-r--r--. 2 sysadmin sysadmin 13 ... a.txt 262184 -rw-r--r--. 2 sysadmin sysadmin 13 ... hard.txtTwo names, and read the first column: the same inode number, 262184. The
2after the permissions is the link count - two directory entries pointing at one file.There is no original and no copy. Writing through one name changes what the other sees:
content of a written through the hard linkAnd deleting the name you created first changes nothing:
content of a written through the hard linkrmdoes not delete files, it removes names. The data goes when the last name goes and the link count reaches zero. That is why deleting a large file can free no space at all - something else still links it, or a running process still holds it open.bash Example session cd ~/fl && ln a.txt hard.txt && ls -li a.txt hard.txt34973609 -rw-r--r--. 2 sysadmin sysadmin 13 Aug 23 10:55 a.txt34973609 -rw-r--r--. 2 sysadmin sysadmin 13 Aug 23 10:55 hard.txtcd ~/fl && echo "written through the hard link" >> hard.txt && cat a.txtcontent of awritten through the hard linkcd ~/fl && rm a.txt && cat hard.txt && ls -li hard.txtcontent of awritten through the hard link34973609 -rw-r--r--. 1 sysadmin sysadmin 43 Aug 23 10:55 hard.txtExpected resultOne inode with two names, surviving the removal of one.
Success conditionYou understand that
rmremoves a name, not a file. -
A soft link is a signpost
262184 -rw-r--r--. 1 sysadmin sysadmin 43 ... hard.txt 262190 lrwxrwxrwx. 1 sysadmin sysadmin 8 ... soft.txt -> hard.txtA different inode, type
l, and a size of 8 - which is the length of the stringhard.txt. A symlink is a small file whose contents are a path.So it breaks when the path stops being true:
lrwxrwxrwx. 1 sysadmin sysadmin 8 ... soft.txt -> hard.txt cat: soft.txt: No such file or directory exit=1 - the symlink still points at a name that is goneThe target was renamed, and the link now points at nothing. Note that
ls -lstill shows the link happily - a dangling symlink is not an error until something follows it.That is the trade. A hard link cannot dangle, because it *is* the file. A symlink can point anywhere, including at another filesystem or at a path that does not exist yet - which is exactly why
/etc/localtimeand most of/usr/bin's alternatives are symlinks.bash Example session cd ~/fl && ln -s hard.txt soft.txt && ls -li hard.txt soft.txt34973609 -rw-r--r--. 1 sysadmin sysadmin 43 Aug 23 10:55 hard.txt35025560 lrwxrwxrwx. 1 sysadmin sysadmin 8 Aug 23 10:55 soft.txt -> hard.txtcd ~/fl && cat soft.txt && readlink soft.txtcontent of awritten through the hard linkhard.txtcd ~/fl && mv hard.txt moved.txt && ls -l soft.txt; cat soft.txt 2>&1; echo "exit=$? - the symlink still points at a name that is gone"lrwxrwxrwx. 1 sysadmin sysadmin 8 Aug 23 10:55 soft.txt -> hard.txtcat: soft.txt: No such file or directoryexit=1 - the symlink still points at a name that is gonecd ~/fl && ln -s /etc/hostname abs.txt && ls -l abs.txt && cat abs.txtlrwxrwxrwx. 1 sysadmin sysadmin 13 Aug 23 10:55 abs.txt -> /etc/hostnamerhcsa-a01Expected resultA link of type l, then the same link dangling after a rename.
Success conditionYou can predict which link type survives a rename.
-
The two things a hard link cannot do
Not a directory:
ln: one: hard link not allowed for directory exit=1Refused outright. Directory hard links would let you build a loop in the filesystem tree, which nothing that walks it could safely handle.
.and..are the only exceptions, and the kernel maintains those.Not across filesystems:
ln: failed to create hard link 'cross.txt' => '/boot/vmlinuz-6.12.0-55.9.1.el10_0.x86_64': Invalid cross-device link exit=1 - /boot is a separate filesystemAn inode number is only unique *within one filesystem*, so a directory entry on
/cannot reference an inode on/boot. "Invalid cross-device link" is the error, and it is worth recognising because it looks like a permissions problem and is not.A symlink has neither restriction:
lrwxrwxrwx. 1 sysadmin sysadmin 3 ... dirsoft -> oneIt is only a path. It does not care what is at the other end, or whether anything is.
bash Example session cd ~/fl && ln one dirlink 2>&1; echo "exit=$? - hard links to directories are refused"ln: one: hard link not allowed for directoryexit=1 - hard links to directories are refusedcd ~/fl && ln /boot/vmlinuz-$(uname -r) cross.txt 2>&1 | head -2; echo "exit=$? - /boot is a separate filesystem"ln: failed to create hard link 'cross.txt' => '/boot/vmlinuz-6.12.0-55.9.1.el10_0.x86_64': Invalid cross-device linkexit=0 - /boot is a separate filesystemcd ~/fl && ln -s one dirsoft && ls -ld dirsoft && ls dirsoft/lrwxrwxrwx. 1 sysadmin sysadmin 3 Aug 23 10:55 dirsoft -> onetwoExpected resultTwo refusals for hard links, and a symlink doing both.
Success conditionYou know when only a symlink will work.
-
Counting names, and finding them
link count: 3Three names for one file. Remove one:
link count after rm: 2The count is the number of directory entries. When it reaches zero the space is freed.
To find every name a file has, search by inode:
./counted.txt ./c3.txtfind . -inumis the only way - there is no list of names on the file itself, only the count. That is the practical limitation of hard links:ls -ltells you a file has three names but not where they are, and if they are on the other side of the filesystem you will not find them without searching the whole thing.bash Example session cd ~/fl && touch counted.txt && ln counted.txt c2.txt && ln counted.txt c3.txt && ls -l counted.txt | awk '{print "link count:", $2}'link count: 3cd ~/fl && rm c2.txt && ls -l counted.txt | awk '{print "link count after rm:", $2}'link count after rm: 2cd ~/fl && find . -inum $(stat -c %i counted.txt) 2>/dev/null./counted.txt./c3.txtExpected resultA link count changing, and both names found by inode.
Success conditionYou can find every name pointing at one file.
Troubleshooting
Invalid cross-device link.Why: A hard link to a different filesystem.
Fix:
ln -s. Check withdf <path>which filesystem each side is on.A symlink shows in
lsbut cannot be read.Why: It is dangling - the target moved or was deleted.
Fix:
readlinkshows the target;ls -lLfails visibly on broken links. Recreate it.Deleting a big file freed no space.
Why: Another hard link, or a process holding it open.
Fix:Check the link count in
ls -l;lsof +L1lists deleted-but-open files.A relative symlink breaks when moved.
Why: It resolves relative to the link's own directory.
Fix:Use an absolute target, or recreate it where it lands.