73

Let's say when I do ls -li inside a directory, I get this:

12353538 -rw-r--r-- 6 me me 1650 2013-01-10 16:33 fun.txt

As the output shows, the file fun.txt has 6 hard links; and the inode number is 12353538.

How do I find all the hard links for the file i.e. files with the same inode number?

its_me
  • 13,959

4 Answers4

72

The basic premise is to use:

find /mount/point -mount -samefile /mount/point/your/file

On systems with findmnt you can derive the mount point like this:

file=/path/to/your/file
find "$(findmnt -o TARGET -cenT "$file")" -mount -samefile "$file"

It's important not to search from / - unless the target file is on that filesystem - because inode numbers are reused in each mounted filesystem.

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
Hauke Laging
  • 90,279
  • 4
    @TheoneManis I just noticed that the other parts of the find call are not necessary. find is clever enough to use them implicitely. Usually you have to give find a search path and have to tell it that is shall not leave these file systems (via symlinks or mount points). But when looking for hard links it's clear on which file system to search. – Hauke Laging Apr 24 '13 at 13:51
  • 7
    Hauke, that depends on the version of find you're using. The GNU version might do that, but the BSD one does not, and this will not work as-is on Mac. – Alan Shutko Apr 24 '13 at 19:36
  • 4
    You may want to add -xdev to avoid descending into directories in other filesystems, otherwise you might find another file with the same inode number located in another filesystem. – mmoya Nov 17 '13 at 17:46
  • The near-equivalent to -samefile on HP-UX is -linkedto (though it is slightly different: a search path must still be specified, for example). – Dominick Pastore Aug 27 '15 at 19:37
  • 3
    Note that if you are not in the root of your mount point, find will explore only subfolders of the current folder. So you should really say something like find /mount/point -samefile /mount/point/your/file – Calimo Apr 06 '17 at 13:30
  • @Calimo You may edit the answer. – Hauke Laging Aug 05 '17 at 06:31
61

If you already have the inode number you can use find's -inum option:

find /mount/point -xdev -inum 12353538

(some find implementations also support -mount as an equivalent of -xdev though only -xdev is standard).

scai
  • 10,793
2

ffind from The Sleuth Kit can find all the file names for an inode, including deleted file names.

For example:

sudo ffind -a /dev/sda3 $(stat --format=%i ~/just_a_test)

yields

* /home/me/empty_1
* /home/me/hard_link_to_empty1
/home/me/just_a_test
/home/me/hard_link_to_just_a_test

The entries with a preceding star are previous file names that don't exist anymore (because the file was renamed or deleted).


I use $(stat --format=%i ~/just_a_test) to get the inode of the file.

To get the partition of the file name programmatically (/dev/sda3 in the previous example), you can use df:

file=~/just_a_test; sudo ffind -a $(df -P "$file" | awk 'END{print $1}') $(stat --format=%i "$file")
0

With thanks to the previous answers.

Note that the stat binary can not only provide one with the inode, but its corresponding device's mount point too. The snippet below uses this to solve the 'usual' use case:

fn_hardlinks() {
  declare target; target="$1" && shift
  [ ! -e "$target" ] && \
    { echo "[error] invalid target: '$target'" 1>&2; exit 1; }
  stat '/' 2>/dev/null 1>&2 || \
    { echo "[error] no functioning 'stat' binary found'" 1>&2; exit 1; }
  declare mount; mount="$(stat -c '%m' "$target")"
  declare inode; inode="$(stat -c '%i' "$target")"
  [ "x${mount[-1]}" != "x/" ] && mount+="/"
  find "$mount" -xdev -inum "$inode" 2>/dev/null
}

alias hardlinks=fn_hardlinks

and running:

> hardlinks ./resources/sphinx/gitinfo.py
/home/user/build/z-documentation/resources/sphinx/gitinfo.py
/home/user/build/sphinx-gitinfo/git/sphinx-gitinfo/gitinfo.py

PS: watch out for exit vs return depending on how you use this!

elbeardmorez
  • 121
  • 3
  • Why fn_hardlinks with an alias rather than a function named hardlinks? – Sinjai Apr 20 '23 at 17:36
  • no reason here - it's a no-op as you rightly allude to. but given the context that this was ripped from a larger pool of utils - because to those unseasoned in shell, function calls don't stand out (hence the prefix), and the alias is for consistency and anticipation of parameterisation – elbeardmorez Apr 27 '23 at 19:42