A good[ish] website

Web development blog, loads of UI and JavaScript topics

Symbolic links and hard links: creating, updating, deleting

Filed under: System— Tagged with: bash, shell

This is a post about understanding linking, symbolic links and hard links.


Symlink, symbolic link, or soft link:

In computing, a symbolic link (also symlink or soft link) is a special type of file that contains a reference to another file or directory in the form of an absolute or relative path and that affects pathname resolution. —Wikipedia

Hard link:

In computing, a hard link is a directory entry that associates a name with a file on a file system. —Wikipedia

Symlink is kind of like having a directory, or a file, in two places at the same time.

If you execute a file in a path ./file-a it actually runs the in location ./file-b.

Hard link syntax:

┌──The link command
│                          ┌──Path to the intended link, can use . or ~
│                          │
│                    ┌─────┴──────┐
ln /path/to/original /path/to/link
            └──Path to the original file/folder can
               use . or ~ or other relative paths


┌──The link command
│   ┌──Create a symbolic link
│   │                         ┌── Path to the intended symlink, can use . or ~
│   │                         │
│   │                   ┌─────┴────────┐
ln -s /path/to/original /path/to/symlink
                └── the path to the original file/folder
                    can use . or ~ or other relative paths

Like the name suggest, soft link is more "fragile" than a hard link.

Here’s stuff one can do with hard links that would break a soft link:

  • Both ends of hard link can be renamed and the link would persist
  • Both ends can be moved around and the link persists
  • The source directory of hard link can be deleted and the target link would persist

Stuff that soft links do that hard links can’t:

  • Reach from a filesystem to another, e.g. from your host machine to a dev VM

Maybe there’s more that I just don’t know, drop a comment if you have something to add.

Technical explanation

There are these things called inodes (Index Nodes) inside your computer, which represent files and directories.

An inode is a data structure on a traditional Unix-style file system such as UFS or ext3. An inode stores basic information about a regular file, directory, or other file system object.

A file is really a link to an inode, hard link creates another file with a link to the same inode.

If you want to test this in practice, you can quickly make some test files with dummy content:

$ mkdir link-test \
  && cd link-test \
  && echo Banana > File_A \
  && echo Apple > File_B \
  && echo Orange > File_C \
  && mkdir Dir_A \
  && mv File_C Dir_A/File_C

That gives you:

├── Dir_A
│   └── File_C
├── File_A
└── File_B

You may see the inode number of File_A:

$ ls -i File_A
51882811 File_A

The stat command shows all the attributes stored into the inode:

$ stat -x File_A
    File: "File_A"
    Size: 7            FileType: Regular File
    Mode: (0644/-rw-r--r--)         Uid: (  501/   bob)  Gid: (   20/   staff)
Device: 1,4   Inode: 51882811    Links: 1Access: Tue Jan 20 09:14:04 2015
Modify: Tue Jan 20 09:14:04 2015
Change: Tue Jan 20 09:14:04 2015

This is on OS X, the output of stat might look a bit different on other systems.

👉 Related: Better stat command.

Files can be deleted based on their inode number:

$ find . -inum 51882811 -exec rm -i {} ;

More here.

Visual explanation

In hard links the link sits, kind of, between the inode and the file:

$ ln File_A File_B

┌──────────┐     ┌──────────┐
│  File_A  |     |  File_B  |
└─────┬────┘     └─────┬────┘
      |                |
      └─────┐  ┌─link──┘
            |  |
      |    inode     |
      | representing |
      |    File_A    |

Where as soft link has nothing to do with the inode, it links between the files like so:

$ ln -s File_A File_B

  ┌──────────┐          ┌──────────┐
  |  File_A  ├───link───|  File_B  |
  └─────┬────┘          └──────────┘
        |    inode     |
        | representing |
        |    File_A    |

Hope I got that right, please correct me in the comments if you have a better explanation.

If link target file name is not specified, the originals name will be used:

$ cd link-test
$ ln -s /path/to/File_B .

That just grabs the File_B from an absolute path and jugs the link into the current directory.

Any sort of a relative path can be used with links if needed:

$ ln -s ../../File_B .

The test directory has one soft link and one hard link. The -l flag on the ls command shows the symlink locations:

$ ls -l
-rw-r--r--  1 bob  staff  7 20 Jan 09:14 File_A
lrwxr-xr-x  1 bob  staff  6 21 Jan 10:32 File_A_link -> File_A-rw-r--r--  2 bob  staff  6 20 Jan 09:14 File_B
-rw-r--r--  2 bob  staff  6 20 Jan 09:14 File_B_link

Or the more explicit command readlink can be used:

$ readlink File_A_link

Hard links aren’t shown the same way, per se. But notice the number 2 before the user name on the ls command output (line highlighted):

$ ls -l
-rw-r--r--  1 bob  staff  7 20 Jan 09:14 File_A
lrwxr-xr-x  1 bob  staff  6 21 Jan 10:32 File_A_link -> File_A
-rw-r--r--  2 bob  staff  6 20 Jan 09:14 File_B-rw-r--r--  2 bob  staff  6 20 Jan 09:14 File_B_link            ^

That means the file has two names, the original and the link. The duplicate files can be viewed with find:

$ find . -xdev -samefile File_B

Here’s the breakdown of the command:

Where to search, current directory in this case.
Don’t descend directories on other filesystems.
-samefile filename
True if the file is a hard link to name. If the command option -L is specified, it is also true if the file is a symbolic link and points to a name.
What to search.
$ rm File_B
$ ls
File_A    File_A_link    File_B_link

The File_B_link persists, essentially it’s now the original File_B.

Either, remove the link:

$ rm File_A_link

Or use unlink:

$ unlink File_A_link

There is a small gotcha here, link the directory and try to remove it, and bash will nag you about it:

$ ln -s Dir_A Dir_A_link
$ unlink Dir_A_link/
unlink: Dir_A_link/: is a directory

The gotcha being the forward slash after the directory name, leave out and it should work:

$ unlink Dir_A_link

Now the File_A_link links to File_A:

$ ls -l File_A_link
lrwxr-xr-x  1 bob  staff  6 21 Jan 15:09 File_A_link -> File_A

Update the link to point to Dir_A/File_C:

$ ln -nsf Dir_A/File_C File_A_link

Symlink can be tested with an if statement. The syntax is:

[ -h FILE ] True if FILE exists and is a symbolic link.

Simple example script:

is_link() {
  local file=$1
  [[ -h $file ]]

if is_link path/to/file; then
  # Do something...


That’s the basics of links, thanks for reading. Please leave a comment if you have something to add.

Comments would go here, but the commenting system isn’t ready yet, sorry.

  • © 2022 Antti Hiljá
  • About
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • I love u!