89

I'm creating a shell script that would take a filename/path to a file and determine if the file is a symbolic link or a hard link.

The only thing is, I don't know how to see if they are a hard link. I created 2 files, one a hard link and one a symbolic link, to use as a test file. But how would I determine if a file is a hard link or symbolic within a shell script?

Also, how would I find the destination partition of a symbolic link? So let's say I have a file that links to a different partition, how would I find the path to that original file?

9
  • 22
    What do you mean by hard link? All files are hard links. Commented Nov 12, 2014 at 17:55
  • 3
    @terdon ln /foo/bar/ /foo/bar2 makes a hardlink while ln -s /foo/bar /foo/bar2 makes a symlink, thats what he means? Commented Nov 12, 2014 at 18:17
  • 16
    @DisplayName yes, but all files are hard links to their inode. That's how Linux file systems work. In your example, bar2 and bar are both hard links, just pointing to the same inode. Commented Nov 12, 2014 at 18:20
  • 12
    @DisplayName yes, they are hard links to other inodes. There is no contradiction here. A file is a link to an inode. That's the definition of a file. In your case, you have these links in different places but that doesn't change the underlying data structure. My point is that both bar and bar2 are equally important. One is not a link to the other, they are both links but point to the same inode. Commented Nov 12, 2014 at 18:28
  • 3
    @Scott no, I'm saying that regular files are hardlinks and that hardlinks created by ln are no different than regular files. Commented Nov 13, 2014 at 18:14

7 Answers 7

65

Jim's answer explains how to test for a symlink: by using test's -L test.

But testing for a "hard link" is, well, strictly speaking not what you want. Hard links work because of how Unix handles files: each file is represented by a single inode. Then a single inode has zero or more names or directory entries or, technically, hard links (what you're calling a "file").

Thankfully, the stat command, where available, can tell you how many names an inode has.

So you're looking for something like this (here assuming the GNU or busybox implementation of stat):

if [ "$(stat -c %h -- "$file")" -gt 1 ]; then echo "File has more than one name." fi 

The -c '%h' bit tells stat to just output the number of hardlinks to the inode, i.e., the number of names the file has. -gt 1 then checks if that is more than 1.

Note that symlinks, just like any other files, can also be linked to several directories so you can have several hardlinks to one symlink.

3
  • ok, just to be clear, i can output the number of hardlinks that the file has using the stat command and if its greater then 1, then it has another file linked somewhere on the partition. Commented Nov 12, 2014 at 18:34
  • 1
    @k-Rocker Yes. Then it has a second name somewhere on the partition. Commented Nov 12, 2014 at 18:38
  • 5
    On OS X or *BSD, it's stat -f %l /path/to/file. You could also use gstat -c %h /path/to/file if you have the GNU coreutils installed without their default names (with Homebrew on OS X). Commented Jul 11, 2016 at 22:28
44

An example:

$ touch f1 $ ln f1 f2 $ ln f1 f3 $ ln -s f1 s1 $ ln -s f2 s2 $ ln -s ./././f3 s3 $ ln -s s3 s4 $ ln s4 s5 $ ls -li total 0 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 f1 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 f2 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 f3 10802345 lrwxrwxrwx 1 stephane stephane 2 Nov 12 19:56 s1 -> f1 10802346 lrwxrwxrwx 1 stephane stephane 2 Nov 12 19:56 s2 -> f2 10802347 lrwxrwxrwx 1 stephane stephane 8 Nov 12 19:56 s3 -> ./././f3 10802384 lrwxrwxrwx 2 stephane stephane 2 Nov 12 19:56 s4 -> s3 10802384 lrwxrwxrwx 2 stephane stephane 2 Nov 12 19:56 s5 -> s3 

The f1, f2 and f3 directory entries are the same file (same inode: 10802124, you'll notice the number of links is 3). They are hard links to the same regular file.

s4 and s5 are also the same file (10802384). They are of type symlink, not regular. They point to a path, here s3. Because s4 and s5 are entries of the same directory, that relative path s3 point to the same file (the one with inod 10802347) for both.

If you do a ls -Ll, that is asking to get file information after resolving symlinks:

$ ls -lLi total 0 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 f1 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 f2 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 f3 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 s1 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 s2 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 s3 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 s4 10802124 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 s5 

You'll find they all resolve to the same file (10802124).

You can check if a file is a symlink with [ -L file ]. Similarly, you can test if a file is a regular file with [ -f file ], but in that case, the check is done after resolving symlinks.

hardlinks are not a type of file, they are just different names for a file (of any type).

You can check whether a file is (hard) linked more than once to one or several directories by checking its link count

POSIXly:

has_more_than_one_link() ( export LC_ALL=C # only locale where the output of ls -n is specified export TZ=UTC0 # simplify the mtime field (which we don't need anyway) # generation as an optimisation. unset -v IFS # for default separator for read to include space and # tab, the only 2 "blank" characters in the C/POSIX locale ls -nqd -- "$1" | { read -r ignore links ignore && [ "$links" -gt 0 ] } ) 

Or with zsh:

has_more_than_one_link() () (($#)) $1(Nl+1) 

Where the body of the function is an anonymous function whose body is (($#)) (returns true if the number of arguments is > 0) and arguments are the expansion of the $1(Nl+1) wildcard, which expands to $1 iff the number of links of that file is greater than 1.

Or using its stat builtin:

zmodload zsh/stat has_more_than_one_link() { stat -LA2 +nlink -- "$1" && (( $2 > 1 )) } 

(here storing the number of links in $2 to avoid having to declare a separate variable local).

Some systems also have an external stat command (which generally post-date the one in zsh), though they all have different interfaces between each other and from zsh's.

With the GNU implementation of stat, you need to handle a file called - specially as GNU stat interprets it as meaning to do a fstat() on stdin:

has_more_than_one_link() { local nlink [ "$1" != - ] || set ./- nlink=$(stat -c %h -- "$1") && [ "$nlink" -gt 1 ] } 

(here assuming a POSIX-like shell with support for local scope).

28

Using the -h and -L operators of the test command:

-h file true if file is a symbolic link -L file true if file is a symbolic link 

http://www.mkssoftware.com/docs/man1/test.1.asp

According to this SO thread, they have the same behavior, but -L is preferred.

4
  • 1
    ok cool, but what about hard links? i checked the tread, but nothing about hard links. If -L returns false, does that mean its a hard link? or just a regular file? Commented Nov 12, 2014 at 17:44
  • 2
    Hard links share the same inode. Also, soft links show an l at the beginning of its ls -l output... I think you may be able to put those rules together in a script, plus the [[ -L file ]] test to see if the given file is either soft or hard. Commented Nov 12, 2014 at 17:46
  • 1
    ok, also, how would i find the destination partition of the symbolic link? Commented Nov 12, 2014 at 18:24
  • 1
    Be cautious of using this since file not exists return false but it doesn't means hard link. Commented Nov 14, 2019 at 18:31
11

There are a lot of quite correct answers here, but I don't think anybody really tackled the original misperception. The original question is basically "when I make a symbolic link, it's easy to identify it afterwards. But I can't figure out how to identify a hard link." And yes, the answers basically boil down to "you can't," and more or less explain why, but nobody seems to have acknowledged that, really, it is confusing and strange.

If you're reading all this and you've figured out what's going on, then you're good; you don't need to read my little bit. If you're still confused, then keep going.

The really really short answer is that a hard link isn't really a link at all, not in the way a symbolic link is. It's a new entry in the directory structure that points to the same bunch of bytes that the original directory entry did, and once you've created it, it is just as "real" and legitimate as the first one. Every "normal" file on your drive has at least one hard link; without that, you wouldn't see it in any directory, and would be unable to refer to it or use it. So if you have a file Fred.txt, and you hard link Wilma.txt and Barney.txt to it, all three names (and directory entries) refer to the same file, and they are all equally valid. There isn't any way for the OS to tell that one of the entries was created when you hit "save" in your text editor, and the others were made with the ln command.

The OS does have to keep track of how many different entries are pointing to the same file, though. If you delete Wilma.txt, no surprise that you don't free up any space on your drive. But if you delete Fred.txt (the "original" file), you still won't free up any space on your drive, because the data on the drive that was known as Fred.txt is still also Barney.txt. Only when you delete all of the directory entries will the OS de-allocate the space that the data itself was occupying.

If Barney.txt had been a symbolic link, then deleting Fred.txt would have de-allocated the space, and Barney.txt would now be a broken link. Also, if you move or rename a file that has a symbolic link pointing at it, you'll break the link. But you can move or rename a hard-linked file all you want without breaking the other directory entries that point to that file/data, because all of them are directory entries that refer to the same block of data on the drive (by using the inode number of that data).

[It's two years later, and that last bit confused me for a minute, so I think I'll clarify. If you command mv ./Wilma.txt ../elsewhere/Betty.txt, it seems like you're moving the file, but in fact, you are not. What you're really doing is removing a line item from the directory list of your current directory, the one that says "the name Wilma.txt is associated with the data that can be found by using inode 1234567," and adding a new line item to the directory list of directory ../elsewhere that says "the name Betty.txt is associated with the data that can be found via inode 1234567". This is why you can "move" a 2 gigabyte file as quickly as a 2 kilobyte file, as long as you're moving them to another location on the same drive.]

Because the OS has to keep track of how many different directory entries are pointing to the same chunk of data, you can tell if a particular file has been hard linked to, even though you can't tell for certain if the directory entry that you're looking at is the "original" one or not. One way is the ls command, specifically ls -l (that's a lower-case L after the dash).

To borrow an earlier example....

 -rw-r--r-- 3 stephane stephane 0 Nov 12 19:55 f1 

The first letter's a dash, so it's not a directory or something else exotic, it's a "regular" ordinary file. But if it were truly ordinary, that number after the rwx-ish part would be 1, as in, "there is one directory entry pointing to this block of data." But it's part of a demonstration of hard links, so instead it says 3.

Note that this can possibly lead to weird and mysterious behavior (if you haven't wrapped your head around hard links, that is). If you open Fred.txt in your text editor and make some changes, will you see the same changes in Wilma.txt and Barney.txt? Maybe. Probably. If your text editor saves the changes by opening the original file and writing the changes to it, then yes, all three names will still be pointing at the same (newly changed) text. But if your text editor creates a new file (Fred-new-temp.txt), writes your changed version to that, then deletes Fred.txt, then renames Fred-new-temp.txt to Fred.txt, Wilma.txt and Barney.txt will still be pointing to the original version, not the new changed version. If you don't understand hard links, this could drive you slightly mad. :) [Okay, I don't actually personally know of any text editors that would do the new-file/rename thing, but I do know of lots of other programs that do exactly that, so stay alert.]

A final note: one of the things that fsck (file system check) checks for is if there are blocks of data on your drive that somehow are no longer referenced by any directory entries. Sometimes something goes wrong, and the only directory entry that points to an inode gets deleted but the drive space itself doesn't get marked as "available." So one of fsck's jobs is to match up all the allocated space with all the directory entries to make sure that there aren't any unreferenced files. If it finds some, it creates new directory entries and puts them in directory lost+found.

4
  • Just wondering, what are those "other programs that do exactly that"? Commented Sep 3, 2016 at 8:45
  • @phk Don't know what he is thinking about specifically, but that is a common enough approach for doing actions which can take a long time and will leave you in an uncertain state if failed. For example, if you try to download from a remote server, and you know there is a chance that the server could timeout, then one approach would be to download the entire contents to a temp file. That way if something goes wrong with the download you still have the original file. Commented Nov 11, 2016 at 21:00
  • 1
    The only program I know for certain is FreeHand, because if/when it crashes during a save, there's a temporary file left behind, rather than a mangled original file. But I've seen other programs do it too; I just can't give you specific examples at this time. Commented Nov 13, 2017 at 22:36
  • "./Wilma.txt ../elsewhere/Betty.txt"" -- your comments are correct ONLY if both locations are o the same filesystem/volume.. if "elsewhere" is a link (hard or soft) to a physically different machine then the outcome is different... Commented Feb 27, 2024 at 11:54
8

you can use readlink FILE; echo $?. This returns 1 when it's a hardlink and 0 when it's a symlink.

From the man page: " When invoked as readlink, only the target of the symbolic link is printed. If the given argu- ment is not a symbolic link, readlink will print nothing and exit with an error."

1
  • 3
    Be cautious of using this since file not exists return 1 but it doesn't means hard link. Commented Nov 14, 2019 at 18:34
0

Try this script to test files, directories and symlinks:

touch non_existing_file.pdf touch existing_regular_file.pdf ln -s non_existing_file.pdf link_non_functional_file.pdf ln -s existing_regular_file.pdf link_functional_file.pdf rm non_existing_file.pdf mkdir existing_directory mkdir non_existing_directory ln -s non_existing_directory link_non_existing_directory ln -s existing_directory link_existing_directory rmdir non_existing_directory for file in non_existing_file.pdf existing_regular_file.pdf link_non_functional_file.pdf link_functional_file.pdf existing_directory link_non_existing_directory link_existing_directory do if [ ! -e $file ] && [ ! -L $file ]; then echo " $file does not exist"; else echo "$file exists somehow"; fi if [ -e $file ] && [ ! -L $file ] && [ ! -d $file ]; then echo " $file is a regular file"; else echo "$file is not a regular file"; fi if [ -e $file ] && [ ! -L $file ] && [ -d $file ]; then echo " $file is a regular directory"; else echo "$file is not a regular directory"; fi if [ -e $file ] && [ -L $file ] && [ ! -d $file ]; then echo " $file is fully functional symlink to a file"; else echo "$file is not a functional link to a file"; fi if [ -e $file ] && [ -L $file ] && [ -d $file ]; then echo " $file is fully functional symlink to a directory"; else echo "$file is not a functional link to a directory"; fi if [ ! -e $file ] && [ -L $file ]; then echo " $file is a broken symlink"; else echo "$file is not a broken symlink"; fi done exit 

The output should be:

 non_existing_file.pdf does not exist non_existing_file.pdf is not a regular file non_existing_file.pdf is not a regular directory non_existing_file.pdf is not a functional link to a file non_existing_file.pdf is not a functional link to a directory non_existing_file.pdf is not a broken symlink existing_regular_file.pdf exists somehow existing_regular_file.pdf is a regular file existing_regular_file.pdf is not a regular directory existing_regular_file.pdf is not a functional link to a file existing_regular_file.pdf is not a functional link to a directory existing_regular_file.pdf is not a broken symlink link_non_functional_file.pdf exists somehow link_non_functional_file.pdf is not a regular file link_non_functional_file.pdf is not a regular directory link_non_functional_file.pdf is not a functional link to a file link_non_functional_file.pdf is not a functional link to a directory link_non_functional_file.pdf is a broken symlink link_functional_file.pdf exists somehow link_functional_file.pdf is not a regular file link_functional_file.pdf is not a regular directory link_functional_file.pdf is fully functional symlink to a file link_functional_file.pdf is not a functional link to a directory link_functional_file.pdf is not a broken symlink existing_directory exists somehow existing_directory is not a regular file existing_directory is a regular directory existing_directory is not a functional link to a file existing_directory is not a functional link to a directory existing_directory is not a broken symlink link_non_existing_directory exists somehow link_non_existing_directory is not a regular file link_non_existing_directory is not a regular directory link_non_existing_directory is not a functional link to a file link_non_existing_directory is not a functional link to a directory link_non_existing_directory is a broken symlink link_existing_directory exists somehow link_existing_directory is not a regular file link_existing_directory is not a regular directory link_existing_directory is not a functional link to a file link_existing_directory is fully functional symlink to a directory link_existing_directory is not a broken symlink 

You can choose the best test for your code.

-1

This didn't work for me:

 if [ "$(stat -c %h -- "$file")" -gt 1 ]; then echo "File has more than one name." fi 

It didn’t work for me, keep it working):

#!/bin/bash if [ "$1" = "" ] then echo "Please enter command argument." exit; fi links=$(stat -c %h "$1" 2>/dev/null) inode=$(stat -c %i "$1" 2>/dev/null) if [ $links -gt 1 ] then echo "File has hard links, select directory to search..."; PS3='Please enter your choice: ' select opt in / /home Quit do case $opt in "/") find / -inum $inode 2>/dev/null break ;; "/home") find /home -inum $inode 2>/dev/null break ;; "Quit") echo "Quit" break ;; *) echo "invalid option $REPLY" ;; esac done else echo "File has no hard links" fi 
4
  • welcome to U&L, can you post on this SO forum in english please ? Commented Feb 1, 2021 at 12:27
  • Certainly can! But I do not see any need for this, since I myself use the translator built into the browser and do not experience any difficulties in understanding! Moreover, as you noticed, the code is written in English. And to translate 2 lines from Russian, if necessary, I think it will not bother anyone. Or do you have a preconceived attitude towards Russian-speaking people? Commented Feb 1, 2021 at 18:31
  • No, we simply have a rule that all content posted on this site needs to be in English. The vast majority of users here are not native English speakers, so everyone needs to translate from their language to English. So please make sure to only post in English in the future. Commented Mar 17, 2021 at 11:27
  • 2
    I tried to translate but it seems like you are just giving solutions that don't work. Please edit the translation so it makes sense. Of course, your script doesn't actually work since you have a syntax error (if foo; then bar; fi not if foo; then do bar; done fi). Commented Mar 17, 2021 at 11:30

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.