Create Hard and Relative Symbolic Links with ln
ln creates hard and symbolic links, the difference that decides whether two file names share one file or quietly drift apart. By the end you will have working examples of both, including a symbolic link whose target is relative to the link's own directory. The examples use GNU ln 9.4 from coreutils, as installed on this machine. Allow about fifteen minutes. You need a shell and write access to the directories where the new link names will live. Nothing here needs elevated privileges unless those directories belong to another user.
The route
Jump straight to the step you need, or tick off Done means at the end.
A link is a directory entry, not a second copy of a file. That distinction matters before you start: changing the contents through a hard link changes the same file, while removing one link name does not remove the file until its last hard link is gone. A symbolic link is a small reference containing a pathname, and it can become dangling if that pathname stops resolving.
1. Check the installed command
Confirm the version and the supported forms before adapting an example:
$ ln --version | head -1
ln (GNU coreutils) 9.4
$ ln --help | sed -n '1,12p'
Usage: ln [OPTION]... [-T] TARGET LINK_NAME
or: ln [OPTION]... TARGET
or: ln [OPTION]... TARGET... DIRECTORY
or: ln [OPTION]... -t DIRECTORY TARGET...
The local manual is the authority for this installed build. GNU coreutils also documents this command online, but distributions can package a different version or patch level.
2. Create a hard link
Make a disposable working directory and a source file. Replace the path with a directory you own when you are working on a real file:
$ mkdir -p "$HOME/ln-demo"
$ printf '%s\n' 'release candidate' > "$HOME/ln-demo/release.txt"
$ ln "$HOME/ln-demo/release.txt" "$HOME/ln-demo/current.txt"
With no -s, ln creates a hard link. Both names refer to the same inode. Verify that directly:
$ stat -c 'inode=%i links=%h name=%n' "$HOME/ln-demo/release.txt" "$HOME/ln-demo/current.txt"
inode=123456 links=2 name=/home/you/ln-demo/release.txt
inode=123456 links=2 name=/home/you/ln-demo/current.txt
$ printf '%s\n' 'updated through current' > "$HOME/ln-demo/current.txt"
$ cat "$HOME/ln-demo/release.txt"
updated through current
The inode number is host-specific, so your number will differ. The matching numbers and link count are the useful checks.
Checkpoint
Stop here if you wanted two independent files. Copying with cp, rather than linking, creates an independent file.
3. Create a symbolic link
Pass -s when the link should store a pathname rather than share the target inode:
$ ln -s "$HOME/ln-demo/release.txt" "$HOME/ln-demo/release-link.txt"
$ readlink "$HOME/ln-demo/release-link.txt"
/home/you/ln-demo/release.txt
$ test -f "$HOME/ln-demo/release-link.txt" && echo 'target resolves'
target resolves
The displayed absolute path is illustrative. readlink prints what the link stores; it does not prove that the target exists, so the test is a separate verification. Symbolic links are useful across filesystems and can point to directories. They are also more fragile: renaming or removing the target can leave a dangling link.
A symbolic link does not grant access that the target's permissions deny. The process opening the link is still subject to normal path resolution and permissions. Be particularly careful when a link points into a service configuration, a deployment directory or a privileged data path.
4. Use a relative link when a tree may move
Absolute targets are clear, but they break if the whole directory tree is moved. For a relative link, put the target and link name in their final directories and add both -s and -r:
$ mkdir -p "$HOME/ln-demo/app/bin" "$HOME/ln-demo/app/releases"
$ printf '%s\n' 'version 1' > "$HOME/ln-demo/app/releases/version.txt"
$ ln -sr "$HOME/ln-demo/app/releases/version.txt" "$HOME/ln-demo/app/bin/version.txt"
$ readlink "$HOME/ln-demo/app/bin/version.txt"
../releases/version.txt
$ cat "$HOME/ln-demo/app/bin/version.txt"
version 1
--relative is meaningful with --symbolic. GNU ln calculates a path from the link location to the target. The relative text is interpreted from the directory containing the link, not from your current shell directory.
Checkpoint
Move a copy of the complete tree only after checking the stored path. A relative link can be moved with its parent tree; moving just the link or just the target can break it.
5. Link several targets into one directory
When the destination directory is explicit, -t avoids repeating its path. Every target must already exist for hard links:
$ mkdir -p "$HOME/ln-demo/selected"
$ printf '%s\n' alpha > "$HOME/ln-demo/a.txt"
$ printf '%s\n' beta > "$HOME/ln-demo/b.txt"
$ ln -v -t "$HOME/ln-demo/selected" "$HOME/ln-demo/a.txt" "$HOME/ln-demo/b.txt"
'/home/you/ln-demo/selected/a.txt' => '/home/you/ln-demo/a.txt'
'/home/you/ln-demo/selected/b.txt' => '/home/you/ln-demo/b.txt'
$ ls -li "$HOME/ln-demo/selected"
The verbose paths are representative; your username and directory will differ. For symbolic links, add -s. If you give several targets without -t, the final argument is treated as the destination directory in the documented multi-target form.
6. Handle an existing destination deliberately
By default, ln refuses to replace an existing destination. That is a useful safety boundary. Inspect the destination before deciding what to do:
$ ln "$HOME/ln-demo/release.txt" "$HOME/ln-demo/current.txt"
ln: failed to create hard link '/home/you/ln-demo/current.txt': File exists
$ ls -l "$HOME/ln-demo/current.txt"
$ readlink "$HOME/ln-demo/current.txt"
The last command prints the target only when the destination is a symbolic link; a diagnostic such as "Invalid argument" for an ordinary file is expected. Do not use -f casually: it removes an existing destination file before creating the new link. That can destroy data and can disrupt a service that reads that pathname.
If replacement is genuinely intended, take a recoverable backup or use interactive mode:
$ ln -i "$HOME/ln-demo/release.txt" "$HOME/ln-demo/current.txt"
ln: replace '/home/you/ln-demo/current.txt'? n
Answering n leaves the existing destination unchanged. For automated replacement, prefer a deployment-specific rollback plan and test it first. The --backup option can preserve an existing destination with the configured backup suffix, usually ~, but the backup itself is another pathname to manage.
7. Undo the examples safely
Removing a link name does not remove the target reached through a symbolic link, and it does not remove the underlying file while another hard link remains. Review the path before removing anything:
$ ls -l "$HOME/ln-demo/current.txt" "$HOME/ln-demo/release-link.txt"
$ rm "$HOME/ln-demo/current.txt" "$HOME/ln-demo/release-link.txt"
$ test ! -e "$HOME/ln-demo/current.txt" && echo 'link name removed'
$ test -e "$HOME/ln-demo/release.txt" && echo 'source still exists'
link name removed
source still exists
rm here changes state and is ordinary-user work inside your own demo directory. Do not broaden it to a wildcard or a variable you have not inspected. If a link was installed for a service, restore the previous link or configuration from the deployment's known-good version, then verify the service separately. Do not use sudo as a first response to a path mistake.
Done means
- Link type known. You know whether each new link is hard or symbolic.
- Both verified. A hard link has the same inode as its target; a symbolic link passes both
readlinkand a target-resolution check. - Relative used correctly. You used
-ronly with-s, and checked the link from its parent directory. - Destination checked first. You checked existing destinations before considering
-for replacement. - Cleanup is safe. You can remove an unwanted link name without accidentally deleting the target file.