ln
Two names for one file, or a pointer to a path
ln -s makes a symbolic link: a small file holding a path as text. Nothing checks that path when
the link is made or afterwards, so a symlink can point at something that does not exist, and a
relative target is read from the directory holding the link rather than from wherever you were
standing when you typed it. That single rule is behind most broken links.
ln without -s makes a hard link, which is a second name for the same data. The two names are
equal: neither is the original, and the data survives until both are gone. It cannot cross a
filesystem and cannot point at a directory.
The difference shows the moment a target is replaced. A symlink resolves the name again and finds whatever is there now; a hard link is still attached to the data it was made from.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
site the tree the cp, mv and rm pages use as well; latest.css is already a symlink to style.css, and assets/ is the subdirectory the relative-target examples make a link in
site:
total 16
drwxr-xr-x 2 user user 4096 Jun 15 10:00 assets
drwxr-xr-x 2 user user 4096 Jun 15 10:00 backups
-rw-r--r-- 1 user user 36 Jun 1 09:00 index.html
lrwxrwxrwx 1 user user 9 Jun 18 09:00 latest.css -> style.css
-rw-r--r-- 1 user user 33 Jun 20 09:00 style.css
site/assets:
total 4
-rw-r--r-- 1 user user 35 Jun 10 09:00 logo.svg
site/backups:
total 0
notes.txt one line, beside site/ rather than inside it
Rebuild before deploying.
Making a symbolic link
ln -s target linkname, in that order. The target is written into the link as text and nothing verifies it.
Create a symlink
ln -s style.css site/newest.css && ls -l site/newest.css
The l in the mode column and the -> are how ls reports a link. The size is the length of the target string.
Show output
Your output will differ: the timestamp is the moment you ran it
lrwxrwxrwx 1 user user 9 Sep 5 08:10 site/newest.css -> style.css
Read the target, then follow it
ln -s site/style.css shortcut.css && readlink shortcut.css && cat shortcut.css
readlink prints the text the link holds and stops there. Every other command follows the link instead, so cat gets the file at the far end.
Show output
site/style.css
body { font-family: monospace; }
Leave the link name out
ln -s site/style.css && readlink style.css
With one argument the link is created in the working directory under the target's own basename. Convenient, and worth spelling out in a script where the reader cannot see your working directory.
Show output
site/style.css
Say what was linked
ln -sv site/style.css link.css
-v (--verbose) prints the pair in the order ln thinks of them, link first. Useful when a script builds the names and you want them in the log.
Show output
'link.css' -> 'site/style.css'
Try to reuse a name
ln -s site/index.html notes.txt; echo "exit $?"
ln will not replace an existing name, whatever kind of thing it is. This refusal is the reason a deploy script that forgets -f works once and fails every run after.
Show output
ln: failed to create symbolic link 'notes.txt': File exists
exit 1
Replace it anyway
ln -sf site/index.html notes.txt && readlink notes.txt
-f (--force) removes the existing name first. notes.txt was a regular file and its contents are gone, so aim this carefully.
Show output
site/index.html
Remove a link without touching the target
ln -s site/style.css link.css && rm link.css; ls link.css; cat site/style.css
rm on a symlink removes the link. Nothing at the far end is disturbed, which is why deleting a link is safe in a way that deleting the wrong file is not.
Show output
ls: cannot access 'link.css': No such file or directory
body { font-family: monospace; }
The target is a string, and it is read from the link
A relative target is resolved from the directory holding the link, not from the directory you ran ln in. The two are the same often enough that the rule goes unnoticed until they are not.
Write a target that looks right and is not
ln -s style.css site/assets/wrong.css && readlink site/assets/wrong.css; cat site/assets/wrong.css
style.css exists, but not inside site/assets, so the link points at nothing. ln accepted it without complaint, and the link looks correct in every listing.
Show output
style.css
cat: site/assets/wrong.css: No such file or directory
Write it from the link's point of view
ln -s ../style.css site/assets/right.css && cat site/assets/right.css
../style.css reads correctly from inside site/assets, which is the only place it is ever read from. An absolute target avoids the question and breaks instead when the tree is moved.
Show output
body { font-family: monospace; }
Link to something that does not exist
ln -s missing.css broken.css && [ -L broken.css ] && echo "the link is there"; [ -e broken.css ] || echo "what it points at is not"
-L asks about the link and -e follows it. A dangling symlink is a legal thing to create, and creating one deliberately is how a package prepares for a file that arrives later.
Show output
the link is there
what it points at is not
Find the broken ones
ln -s style.css site/assets/wrong.css && find site -xtype l
-xtype l resolves each link and matches the ones that still report as a link afterwards, which only a broken target does. find -type l lists every link instead, broken or not.
Show output
site/assets/wrong.css
Hard links
ln with no -s gives the same data a second name. Neither name is the original, and the file survives until the last one is removed.
Make a second name for a file
ln notes.txt copy.txt && stat -c "%h %n" notes.txt copy.txt
%h is the link count, and both names report 2 because both are counted by the same file. Nothing was copied and no extra disk was used.
Show output
2 notes.txt
2 copy.txt
Confirm two names are one file
ln notes.txt copy.txt && [ notes.txt -ef copy.txt ] && echo "the same file under two names"
-ef compares the device and inode rather than the contents, so it answers yes only for names that really are the same file. Two files with identical bytes answer no.
Show output
the same file under two names
Write through one name
ln notes.txt copy.txt && printf "second line\n" >> copy.txt && cat notes.txt
There is one file, so there is one set of contents. An editor that saves by writing a new file and renaming it over the old one breaks the link instead, which is why this surprises people.
Show output
Rebuild before deploying.
second line
Remove one name
ln notes.txt copy.txt && rm notes.txt && stat -c "%h %n" copy.txt && cat copy.txt
The count drops to 1 and the data stays. Removing a name is not removing a file: the kernel frees the data when the last name goes and nothing has it open.
Show output
1 copy.txt
Rebuild before deploying.
See what an ordinary file counts
stat -c "%h %n" site/style.css
Every file has at least one link, its own name in a directory. A hard link is not a special kind of thing, it is a second entry pointing at what was already there.
Show output
1 site/style.css
Try to hard link a directory
ln site copy-dir; echo "exit $?"
Refused, because a second name for a directory would let a tree contain itself and nothing that walks a filesystem would terminate. ln -s on a directory is fine and is what you want.
Show output
ln: site: hard link not allowed for directory
exit 1
Try to hard link across filesystems
ln notes.txt /dev/shm/elsewhere.txt; echo "exit $?"
A hard link is an entry in a directory pointing at an inode number, and inode numbers mean nothing outside their own filesystem. /dev/shm is a separate one, so this cannot work however the paths are written.
Show output
ln: failed to create hard link '/dev/shm/elsewhere.txt' => 'notes.txt': Invalid cross-device link
exit 1
Choosing between them
The two behave alike until the target is replaced, which is the moment to understand before picking one.
Replace the target and watch them disagree
ln notes.txt hard.txt && ln -s notes.txt soft.txt && printf "replaced\n" > new.txt && mv new.txt notes.txt && echo "-- hard:" && cat hard.txt && echo "-- soft:" && cat soft.txt
mv put a different file at the name notes.txt. The hard link is attached to the data and still has the old contents; the symlink resolves the name again and finds the new ones. This is the whole difference.
Show output
-- hard:
Rebuild before deploying.
-- soft:
replaced
Test which kind you are holding
ln -s site/style.css link.css && [ -L link.css ] && echo "it is a symlink"; [ -f link.css ] && echo "and -f follows it to a regular file"
-L is the only test that does not follow the link. A hard link answers no to -L, because from the filesystem's point of view it is simply the file.
Show output
it is a symlink
and -f follows it to a regular file
Resolve a link to the real path
ln -s site/style.css link.css && basename "$(readlink -f link.css)"
readlink -f follows every link in the path and returns an absolute answer, where a bare readlink gives you the one string the link holds. basename is here because the absolute path depends on where you are.
Show output
style.css
Count the links in a tree
find site -type l | sort
-type l matches the link itself rather than following it. Sorting matters because find walks directory order, which is not an order you can predict.
Show output
site/latest.css
Repointing a link that names a directory
The current -> releases/v3 arrangement is how a deploy switches versions. Repointing that link has a trap in it.
Repoint it the obvious way
mkdir -p releases/v1 releases/v2 && ln -s releases/v1 current && ln -sf releases/v2 current && readlink current; ls current
current still points at v1, and there is now a v2 inside it. -f removed nothing, because ln followed the existing link to a directory and made the new link inside that directory.
Show output
releases/v1
v2
Repoint it correctly
mkdir -p releases/v1 releases/v2 && ln -s releases/v1 current && ln -sfn releases/v2 current && readlink current
-n (--no-dereference) treats an existing symlink as the thing to replace rather than as a directory to descend into. Any script that moves a current link wants -sfn and not -sf.
Show output
releases/v2
Mind the trailing slash when removing one
ln -s site dirlink && rm dirlink/; echo "exit $?"
A trailing slash asks for the directory the link names, and rm will not remove a directory. Drop the slash and the link goes; tab completion is what adds it.
Show output
rm: cannot remove 'dirlink/': Is a directory
exit 1