Chapter 04Lesson 03~50 minutes

Hard Links, Symbolic Links, and Inodes

Links become simple once names and objects are separated. A directory entry maps a name to an inode; a hard link adds another mapping to the same inode, while a symbolic link is a distinct object containing a pathname.

BeginnerInodes and linksHands-on lab

Learning objectives

By the end of this lesson

  • Explain how directory entries and inodes represent regular files.
  • Create and inspect hard links that share one inode and data stream.
  • Create relative and absolute symbolic links and resolve their targets.
  • Predict behavior when original names, hard links, targets, or symbolic links are removed.
  • Apply a symbolic-link release-switch pattern with verification and rollback awareness.

1. Names live in directories; file metadata lives in inodes

A directory stores mappings from names to filesystem objects. For many Unix-style filesystems, a regular file’s inode contains metadata and references to its data. The filename is not stored as the file’s unique identity; it is a directory entry that points to the inode.

Hard-link and symbolic-link relationships
flowchart TD
  D1["directory entry: config"] --> I["inode 4821"]
  D2["directory entry: config.backup"] --> I
  I --> B["file data blocks"]
  S["symlink inode"] --> P["stored path: config"]
  P -. resolved through directory .-> D1
Identity pair

An inode number alone is not globally unique. The filesystem device and inode together identify an object at a point in time, and inode numbers may be reused after deletion.

4. Inspect the link itself and the resolved target separately

Many commands follow symbolic links by default in some contexts and inspect the link itself in others. State your intent. GNU stat inspects the link itself by default and follows it with -L or --dereference.

printf 'Symbolic-link object:\n'
stat -- "$lab/current"

printf '\nResolved target object:\n'
stat -L -- "$lab/current"

printf '\nCompact comparison:\n'
stat -c 'no-follow: type=%F inode=%i links=%h name=%n -> %N' -- "$lab/current"
stat -Lc 'follow:    type=%F inode=%i links=%h name=%n' -- "$lab/current"

readlink prints stored link text. realpath resolves components and normally requires a resolvable target. During incident analysis, capture both: the configured reference and where it resolves now.

5. Unlinking a name does not necessarily remove file data immediately

Removing one hard-link pathname decrements the link count. The inode and data remain while another hard link exists. Even after the final pathname is unlinked, an already-open file can remain accessible to the process holding its file descriptor until that descriptor closes.

printf 'Before unlink:\n'
stat -c 'path=%n inode=%i links=%h' -- "$lab/config" "$lab/config.backup"

rm -- "$lab/config"
printf '\nAfter removing one name:\n'
stat -c 'path=%n inode=%i links=%h' -- "$lab/config.backup"
cat -- "$lab/config.backup"

rm -- "$lab/releases/1.0.0/VERSION"
printf '\nDangling symbolic link evidence:\n'
ls -ld -- "$lab/current"
if ! test -e "$lab/current"; then
  printf 'target is not resolvable, but link object still exists\n'
fi
test -L "$lab/current" && printf 'link object exists\n'
Disk space and deleted open files

A large log can be unlinked yet continue consuming space while a service keeps it open. Tools such as lsof +L1 can reveal open files whose link count is zero.

6. Symbolic links as release selectors

A common layout stores immutable release directories and points a stable name such as current to the active release. A new link can be constructed and then renamed into place, reducing the window in which readers could observe an incomplete update.

root="$lab"
next_target='releases/1.1.0'
temporary_link="$root/.current.next"

rm -f -- "$temporary_link"
ln -s -- "$next_target" "$temporary_link"

# Verify before switching.
test "$(cat "$temporary_link/VERSION")" = '1.1.0'
readlink -- "$temporary_link"

# Rename the prepared link over the stable selector.
mv -Tf -- "$temporary_link" "$root/current"

printf 'new stored target: %s\n' "$(readlink "$root/current")"
printf 'new active version: %s\n' "$(cat "$root/current/VERSION")"

The exact atomicity and overwrite behavior depend on platform and filesystem semantics. Keep old release directories until health checks pass so rollback can repoint the selector.

7. Hands-on lab: prove link behavior step by step

set -u
root="$HOME/devops-academy/linux/chapter04/lesson03-proof"
rm -rf -- "$root"
mkdir -p -- "$root/data"
printf 'payload-a\n' > "$root/data/object"

ln -- "$root/data/object" "$root/data/object.hard"
ln -s -- data/object "$root/object.symbolic"

report="$root/link-report.txt"
{
  printf '=== initial identities ===\n'
  stat -c '%n device=%d inode=%i links=%h type=%F' -- \
    "$root/data/object" "$root/data/object.hard" "$root/object.symbolic"

  printf '\n=== symlink target ===\n'
  readlink -- "$root/object.symbolic"
  realpath -- "$root/object.symbolic"

  printf '\n=== edit through hard-link name ===\n'
  printf 'payload-b\n' >> "$root/data/object.hard"
  cat -- "$root/data/object"

  printf '\n=== remove first hard-link name ===\n'
  rm -- "$root/data/object"
  stat -c '%n inode=%i links=%h' -- "$root/data/object.hard"

  printf '\n=== symlink after target-name removal ===\n'
  test -L "$root/object.symbolic" && printf 'link object=yes\n'
  test -e "$root/object.symbolic" || printf 'resolved target=no\n'
} > "$report"

cat -- "$report"

Verification checklist

8. Common link mistakes

Calling one hard link the original

All hard-link names are peers that reference the same inode.

Creating the wrong relative target

A relative symlink target is interpreted from the directory containing the link, not from the shell’s current directory during later access.

Assuming removing a symlink removes its target

rm link removes the link object. It does not normally remove the resolved target.

Following links unintentionally in automation

Archivers, copy tools, find operations, and security checks have different link-following options. Choose them explicitly.

9. Knowledge check

Question 1. What do two hard-link names share?

Question 2. From where is a relative symbolic-link target interpreted?

Question 3. How can a symbolic link exist while test -e link reports false?

10. Summary

Directories map names to filesystem objects. Hard links are multiple directory entries for one inode, so they share content and metadata. Symbolic links are separate objects that store pathnames and can cross filesystems or become dangling. Correct inspection distinguishes the link object from its resolved target, and reliable release switching verifies a prepared link before publishing it.

Next lesson

Globbing, Brace Expansion, and Safe Bulk Operations

You will control how the shell generates multiple pathnames before file commands receive their arguments.

11. Further reading

  • GNU Coreutils manuals for ln, readlink, realpath, stat, mv, rm, and ls.
  • Linux manual pages for link, symlink, unlink, rename, inode, path_resolution, and open.
  • POSIX specifications for link, symlink, readlink, and pathname resolution.
  • Filesystem documentation for hard-link limits, rename semantics, and inode allocation.
  • lsof documentation — inspection of open, unlinked files.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.