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.
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.
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.
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.
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.
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.
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.
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.
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.
readlink and a target-resolution check.-r only with -s, and checked the link from its parent directory.-f or replacement.