Home / Alt manpages / link(1)

  • link(1)
  • User command
  • linux

Create a Hard Link Safely with link on Linux

You will create a second directory entry for an existing file, prove that both names refer to the same inode, and remove the extra name without deleting the file data. The examples use GNU coreutils 9.4, the version installed on this machine.

Allow about ten minutes. You need a shell and write access to a test directory. No command in the main workflow needs sudo. You need a filesystem that supports hard links, which is normal for local Linux filesystems.

1. Check the installed command

Confirm the binary and its supported interface before using it. These are read-only commands:

$ command -v link
/usr/bin/link
$ link --version
link (GNU coreutils) 9.4
$ link --help
Usage: link FILE1 FILE2
  or:  link OPTION

The positional arguments are easy to reverse. FILE1 must already exist. FILE2 is the new name that link will create. The command does not copy bytes and does not create a symbolic link.

Checkpoint: if link --version reports a different implementation or version, keep the local help output as the authority for its syntax. The rest of this guide describes the GNU coreutils command documented by the installed link(1) page.

2. Prepare a disposable test file

Use a new temporary directory so the example cannot collide with a useful file in your home directory. This setup changes state only inside the directory it prints:

$ workdir=$(mktemp -d /tmp/link-demo.XXXXXX)
$ source="$workdir/source.txt"
$ alias="$workdir/second-name.txt"
$ printf 'original content\n' > "$source"
$ printf 'source: %s\nalias:  %s\n' "$source" "$alias"
source: /tmp/link-demo.A1b2C3/source.txt
alias:  /tmp/link-demo.A1b2C3/second-name.txt

The directory suffix is generated by mktemp, so your output will differ. The redirection creates a new file in the private temporary directory. Do not replace $source with a path to an important file until you have checked the destination carefully.

3. Create the second name

Pass the existing file first and the new name second:

$ link "$source" "$alias"
$ stat -c 'inode=%i links=%h name=%n' "$source" "$alias"
inode=123456 links=2 name=/tmp/link-demo.A1b2C3/source.txt
inode=123456 links=2 name=/tmp/link-demo.A1b2C3/second-name.txt

The inode number is deliberately shown as an example value because it is allocated by the filesystem. The important checks are that both lines show the same inode and that the link count is at least two. An exit status of zero means the link system call succeeded.

Both names now address the same file data. Verify that by reading the new name:

$ cat "$alias"
original content

There is no elevated-privilege step here. The directory must be writable by your user, and the source must be accessible. The source file's mode does not grant permission to create the directory entry; directory permissions control that part of the operation.

A hard link is another name for the same inode. Editing through either name changes the same underlying file, subject to the usual file permissions. Removing one name does not remove the data while another hard link still exists:

$ printf 'added through the second name\n' >> "$alias"
$ cat "$source"
original content
added through the second name
$ rm "$alias"
$ test ! -e "$alias" && echo 'second name removed'
second name removed
$ cat "$source"
original content
added through the second name

Warning

rm changes directory state. In this example it removes only the disposable second name, but a typo in a real path can remove a useful directory entry. Check the path with printf '%s\n' "$alias" before running it. The original content remains under $source because that is a separate link to the inode.

5. Avoid overwriting and accidental replacement

link does not replace an existing destination. That is a useful safety boundary: if FILE2 already exists, the command fails instead of silently unlinking it.

$ printf 'do not replace me\n' > "$alias"
$ link "$source" "$alias"
link: cannot create link '/tmp/link-demo.A1b2C3/second-name.txt' to '/tmp/link-demo.A1b2C3/source.txt': File exists
$ printf 'exit status: %s\n' "$?"
exit status: 1

The wording includes your actual temporary path and can vary slightly with the coreutils build. Check the non-zero status and confirm that the existing file still contains what you expect. If you intend to replace a destination, stop and decide explicitly whether it is safe to remove that name first. Do not turn this into a blind batch operation.

6. Recognise the common boundaries

Hard links are directory entries on one filesystem. If the source and destination directories are on different mounted filesystems, the kernel normally rejects the operation with an error such as Invalid cross-device link. Check the devices without changing anything:

$ df -P "$source" "$alias"
Filesystem  1024-blocks  Used Available Capacity Mounted on
/dev/...          ...     ...       ...      ... /
/dev/...          ...     ...       ...      ... /other-mount

The exact filesystem names and numbers are host-specific. A separate device or mount point is a warning that a hard link may not be possible. If you need two names across filesystems, consider a symbolic link or a real copy, but choose that replacement deliberately because those have different behaviour.

Do not use link for directories. Linux prevents ordinary users from hard-linking directories because directory links could break filesystem traversal. Use a symbolic link for a directory when that is genuinely what you need, and review the permissions and cleanup implications first.

Hard links also do not cross the boundary of a container, chroot or other filesystem view unless both paths resolve to the same underlying filesystem visible to that environment. A path that looks local in two shells is not enough evidence; the kernel decides from the resolved mounts.

7. Finish the disposable test

If you recreated $alias for the failed-overwrite example, remove that test name and then remove the now-empty temporary directory. Both commands are destructive, so print the paths first:

$ printf 'will remove: %s\n' "$alias" "$workdir"
will remove: /tmp/link-demo.A1b2C3/second-name.txt
will remove: /tmp/link-demo.A1b2C3
$ rm -- "$alias"
$ rmdir -- "$workdir"
$ test ! -e "$workdir" && echo 'test directory removed'
test directory removed

If the directory is not empty, stop and inspect it with find "$workdir" -maxdepth 1 -mindepth 1 -print rather than adding a recursive removal flag. If you need to keep the test for later inspection, leave the directory in place and remove it only after checking its contents.

Done means

  • You confirmed that the installed command is GNU coreutils link and checked its version.
  • You passed the existing file as FILE1 and a new path as FILE2.
  • stat showed the same inode through both names and an increased link count.
  • You know that edits through either name affect the same file data.
  • You checked destinations before using state-changing cleanup commands.
  • You can distinguish a hard link from a symbolic link, a copy and a cross-filesystem workaround.