Skip to content

104.6 Create and change hard and symbolic links

Weight: 2

Candidates should be able to create and manage hard and symbolic links to a file.

Objectives

  • Create links.
  • Identify hard and/or softlinks.
  • Copying versus linking files.
  • Use links to support system administration tasks.

Terms

ln, unlink

On a storage device, a file or directory is saved on some block and a reference to it is saved in the FAT, ext or other allocation table, alongside data about its owner, permissions, when it was last accessed, its size and such. As you already know, we can check this using the ls command.

$ ls -i script.sh
785379 script.sh
$ ls -l script.sh
-rwxr--r-t 1 nagato adm 34 Mar  5 13:38 script.sh
$ ls -R
.:
check_tr181_file.php  etc_apparmom.tar  index.html  php_checker  script.sh  test.csv  w

./php_checker:
check_tr181_file.php  index.html  test.csv

./w:

That 785379 from ls -i is the inode number. An inode is a data structure holding the attributes of a file: its permissions, its owner, and which blocks on the disk hold its data. The name "inode" comes from "index node", because it works like an entry in an index.

The key idea for this whole objective is that a filename and a file are two different things:

   directory entry          inode              data blocks
   "script.sh"    ------>   785379   ------>   the actual bytes
                              ^
   a link is just another     |
   directory entry pointing --+
   at the SAME inode

But it is also possible to create links. A link is simply an additional directory entry for a file or directory:

$ ls -l
total 4
-rwxr-xr-x 1 nagato adm 34 Mar  5 13:38 script.sh
$ ln -s script.sh copy_of_script.sh
$ ls -ltrh
total 4.0K
-rwxr-xr-x 1 nagato adm  34 Mar  5 13:38 script.sh
lrwxrwxrwx 1 nagato nagato  9 Mar  6 11:31 copy_of_script.sh -> script.sh
$ ls -i
785349 copy_of_script.sh  785379 script.sh

Note the two different inode numbers in that last line, 785349 and 785379. That is the giveaway that this is a soft link. A soft link is its own separate file with its own inode, and its contents are just the name of the target.

A link is simply an additional directory entry for a file or directory, allowing two or more names for the same file. A hard link is a directory entry that points to an inode, while a soft link or symbolic link is a directory entry that points to an inode that provides the name of another directory entry. The exact mechanism for storing the second name may depend on both the file system and the length of the name. Symbolic links are also called symlinks.

Soft links, or symlinks, merely point to another file or directory by name rather than by inode. Soft links can cross file system boundaries.

$ vi new_file
$ ls -ltrh
total 4.0K
-rw------- 1 nagato nagato 9 Mar  6 11:37 new_file
$ ln new_file hard_link
$ ln -s new_file soft_link
$ ls -ltrh
total 8.0K
-rw------- 2 nagato nagato 9 Mar  6 11:37 new_file
-rw------- 2 nagato nagato 9 Mar  6 11:37 hard_link
lrwxrwxrwx 1 nagato nagato 8 Mar  6 11:37 soft_link -> new_file
$ rm new_file
$ ls -ltrh
total 4.0K
-rw------- 1 nagato nagato 9 Mar  6 11:37 hard_link
lrwxrwxrwx 1 nagato nagato 8 Mar  6 11:37 soft_link -> new_file
$ cat hard_link
new file
$ cat soft_link
cat: soft_link: No such file or directory

Note the l as the first character in ls -l's permission list when we have a symbolic link.

This one block is the most important thing in the objective, so let me pull it apart.

Before deleting. Look at the number right after the permissions, which is the link count:

-rw------- 2 nagato nagato 9 Mar  6 11:37 new_file      <- count is 2
-rw------- 2 nagato nagato 9 Mar  6 11:37 hard_link     <- count is 2
lrwxrwxrwx 1 nagato nagato 8 Mar  6 11:37 soft_link     <- count is 1

Making the hard link raised the count on both names from 1 to 2, because two directory entries now point at that inode. The soft link did not change it, because it points at a name, not at the inode.

After deleting new_file. The hard link still works and cat hard_link prints the contents. The soft link is now broken, and cat soft_link fails.

   hard link                       soft link

   new_file  --+                   soft_link --> "new_file"
               |--> inode 785379                     |
   hard_link --+    (data)         new_file ---> inode (data)

   delete new_file:                delete new_file:
   one entry gone, count 2->1      the NAME is gone
   the data is still reachable     soft_link points at nothing
   through hard_link               = dangling link

Deleting a file does not erase the data. It removes one directory entry and lowers the link count. Only when the count reaches zero does the space get released. That is why a hard link survives.

The syntax and a few rules. The general form is ln TARGET LINK_NAME:

$ ln target.txt /home/carol/Documents/hardlink
$ ln -s target.txt /home/carol/Documents/softlink

The target must already exist. If you leave out LINK_NAME, a link with the same name as the target is created in the current directory.

The link count rule: every file starts at 1, every directory starts at 2, and each new hard link adds one. So which name is the "real" one?

$ ls -li
total 224
3806696 -r--r--r-- 2 carol carol 111702 Jun 7 10:13 hardlink
3806696 -r--r--r-- 2 carol carol 111702 Jun 7 10:13 target.txt

Same inode number on both lines. There is no way to tell which was created first, and it does not matter. They are equal names for the same file.

One more detail about permissions. A soft link always displays as lrwxrwxrwx no matter what, but those permissions mean nothing. What actually applies are the permissions of the target.

You can create hard links only for files, not for directories. The exception is the special directory entries in a directory for the directory itself and for its parent (. and ..)

You will get an error if you attempt to create hard links that cross file systems or that are for directories.

$ ln mydir link2dir    # ln: mydir: hard link not allowed for director
$ ln -s mydir link2dir # works just fine

The two limits on hard links come straight from how inodes work. An inode number is only unique inside one filesystem, so a hard link cannot reach across to another partition or disk. And directories are barred because a hard link to a directory could create a loop in the tree that tools like find would never escape.

                    hard link      soft link
   ------------------------------------------------
   crosses filesystems     no          yes
   works on directories    no          yes
   own inode               no          yes
   survives target delete  yes         no
   raises link count       yes         no

If you are using relative names, you will usually want the current working directory to be the directory where you are creating the link. Otherwise the link you create will be relative to another point in the file system.

$ ln -s myfile.txt mydir/ #broken link
$ cd mydir
$ ln -s ../myfile.txt .

It is recommended to use the exact path for links.

$ ln -s /tmp/myfile.txt /tmp/Foo/Bar/myfile.txt
$ ls -l /tmp/Foo/Bar/myfile.txt
lrwxrwxrwx. 1 nagato nagato 9 Mar  6 12:37 /tmp/Foo/Bar/myfile.txt -> /tmp/myfile.txt

Here is exactly why this matters. A relative link works fine where it was made:

$ ln -s original.txt softlink
$ ls -lh
total 112K
-r--r--r-- 1 carol carol 110K Jun 7 10:13 original.txt
lrwxrwxrwx 1 carol carol 12 Jun 7 19:23 softlink -> original.txt

But move the link and it breaks:

$ mv softlink ../
$ less ../softlink
../softlink: No such file or directory

The link stores the literal text original.txt, and that name is looked up relative to wherever the link currently sits. Using an absolute path fixes it permanently:

$ ln -s /home/carol/Documents/original.txt softlink
$ ls -lh
lrwxrwxrwx 1 carol carol 40 Jun 7 19:34 softlink -> /home/carol/Documents/original.txt

Note the size column. A soft link's size is just the number of characters in the path it stores, which is why it went from 12 to 40.

Hard links have no such problem. Because they point at an inode rather than a path, they can be moved anywhere in the filesystem with mv and will never break.

We can find symbolic links using the find command:

$ find . -type l

That is the -type l from 103.3, alongside f for files and d for directories.

They are highly used to keep one specific name pointing to a changing binary name. For example we always need to be able to run python3, so we point it to the latest installed python on the system:

$ which python3
/usr/bin/python3
$ ls -l /usr/bin/python3
lrwxrwxrwx 1 root root 10 Mar 25  2022 /usr/bin/python3 -> python3.10

This is the classic real world use. Scripts say #!/usr/bin/python3 and keep working when the system upgrades from 3.10 to 3.11, because only the link has to be repointed. The same trick is why /bin/sh points at dash on Debian, which came up back in 103.1.

Another common one: a service's config or data directory kept somewhere with more disk space, with a symlink left in the expected place so the software never notices.

To remove links, you can use the rm or unlink command.

Both kinds of link are ordinary enough to be renamed or moved with mv and deleted with rm. unlink does the same job as rm for a single file, and its name is literal: it removes one directory entry, which is exactly what deleting has always meant.

Copying versus linking, which is one of the stated objectives:

   cp file copy       makes a SECOND copy of the data
                      uses twice the disk space
                      editing one does not change the other

   ln file hardlink   makes a second NAME for the same data
                      uses no extra space
                      editing through either name changes both

Summary

I have a Linux system where a filename and a file are two separate things. The name lives in a directory entry, and the actual file is an inode holding the permissions, the ownership and the location of the data on disk. A link is just another directory entry, so one file can have several names, and ls -i shows me the inode number that tells me whether two names really are the same file.

There are two kinds. A hard link, made with plain ln, is another name pointing at the same inode. Every hard link raises the file's link count, the number shown right after the permissions in ls -l, and files start at 1 while directories start at 2. Because deleting a name only removes an entry and lowers that count, the data survives as long as one name remains. Hard links can only point to files, never directories, and can never cross from one filesystem to another, because an inode number only means something inside a single filesystem.

A symbolic link, made with ln -s, is a small file of its own with its own inode, and its contents are simply the path of the target. ls -l marks it with an l in the first column and shows the target after an arrow. Symlinks can point at directories and can cross filesystems, which is why they are used far more often in practice. Their weakness is that they point at a name: if the target is deleted or renamed the link dangles and stops working, and if I create it with a relative path it will break the moment I move it. Writing the full absolute path avoids that. The permissions on a symlink always show as rwxrwxrwx and mean nothing, since what applies are the permissions of the target.

Both kinds are removed with rm or unlink, and both can be renamed and moved with mv. The reason I use them at all is that a link costs no extra disk space, unlike cp which duplicates the data. The most common real use is a stable name in front of a changing one, like /usr/bin/python3 pointing at python3.10, so everything that depends on the stable name keeps working after an upgrade. To find symlinks on a system, find . -type l lists them.