Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I run

ln /a/A /b/B

I would like to see at the folder a where the file A points to by ls.

share|improve this question

migrated from stackoverflow.com Jul 25 '09 at 22:53

This question came from our site for professional and enthusiast programmers.

    
Hard links aren't pointers, symlinks are. They're multiple names for the same file (inode). After a link(2) system call, there's no sense in which one is the original and one is the link. This is why, as the answers point out, the only way to find all the links is find / -samefile /a/A. Because one directory entry for an inode doesn't "know about" other directory entries for the same inode. All they do is refcount the inode so it can be deleted when the last name for it is unlink(2)ed. (This is the "link count" in ls output). –  Peter Cordes Apr 21 at 18:47

9 Answers 9

up vote 70 down vote accepted

You can find inode number for your file with

ls -i

and

ls -l

shows references count (number of hardlinks to particular inode)

after you found inode number, you can search for all files with same inode:

find . -inum NUM

will show filenames for inode NUM in current dir (.)

share|improve this answer
13  
you could just run find . -samefile filename –  BeowulfNode42 Nov 25 '13 at 0:02

There isn't really a well-defined answer to your question. Unlike symlinks, hardlinks are indistinguishable from the original file. Filenames in directories are just references to an inode (which contains the file contents and file attributes). Creating a hard link creates another reference to the same inode. These references are unidirectional (in typical filesystems, at least) -- the inode only keeps a reference count.

This means that the only way to find the other references to a given inode is to exhaustively search over the file system checking which files refer to the inode in question. You can use 'test A -ef B' from the shell to perform this check.

share|improve this answer
19  
That means that there is no such thing as a hard link to another file, as the original file is also a hard link; hard links point to a location on disk. –  jtbandes Jul 26 '09 at 0:03
3  
@jtbandes: Hard links point to an inode which points to the actual data. –  dash17291 Jun 13 '13 at 19:34

UNIX has hard links and symbolic links (made with "ln" and "ln -s" respectively). Symbolic links are simply a file that contains the real path to another file and can cross filesystems.

Hard links have been around since the earliest days of UNIX (that I can remember anyway, and that's going back quite a while). They are two directory entries that reference the exact same underlying data. The data in a file is specified by its inode. Each file on a file system points to an inode but there's no requirement that each file point to a unique inode - that's where hard links come from.

Since inodes are unique only for a given filesystem, there's a limitation that hard links must be on the same filesystem (unlike symbolic links). Note that, unlike symbolic links, there is no privileged file - they are all equal. The data area will only be released when all the files using that inode are deleted (and all processes close it as well, but that's a different issue).

You can use the "ls -i" command to get the inode of a particular file. You can then use the "find <filesystemroot> -inum <inode>" command to find all files on the filesystem with that given inode.

Here's a script which does exactly that. You invoke it with:

findhardlinks ~/jquery.js

and it will find all files on that filesystem which are hard links for that file:

pax@daemonspawn:~# ./findhardlinks /home/pax/jquery.js
Processing '/home/pax/jquery.js'
   '/home/pax/jquery.js' has inode 5211995 on mount point '/'
       /home/common/jquery-1.2.6.min.js
       /home/pax/jquery.js

Here's the script.

#!/bin/bash
if [[ $# -lt 1 ]] ; then
    echo "Usage: findhardlinks <fileOrDirToFindFor> ..."
    exit 1
fi

while [[ $# -ge 1 ]] ; do
    echo "Processing '$1'"
    if [[ ! -r "$1" ]] ; then
        echo "   '$1' is not accessible"
    else
        numlinks=$(ls -ld "$1" | awk '{print $2}')
        inode=$(ls -id "$1" | awk '{print $1}' | head -1l)
        device=$(df "$1" | tail -1l | awk '{print $6}')
        echo "   '$1' has inode ${inode} on mount point '${device}'"
        find ${device} -inum ${inode} 2>/dev/null | sed 's/^/        /'
    fi
    shift
done
share|improve this answer
    
@pax: There seems to be a bug in the script. I start it by . ./findhardlinks.bash while being in OS X's Zsh. My current window in Screen closes. –  Masi Jul 25 '09 at 16:31
3  
@Masi The issue is your initial . (same as the source command). That causes the exit 1 command to exit your shell. Use chmod a+x findhardlinks.bash then execute it with ./findhardlinks.bash or use bash findhardlinks.bash –  njsf Jul 25 '09 at 23:08
    
Please, see my reply to your answer at superuser.com/questions/12972/to-see-hardlinks-by-ls/… –  Masi Jul 26 '09 at 16:42
1  
To do this programmatically, it's probably more resilient if you use this instead: INUM=$(stat -c %i $1). Also NUM_LINKS=$(stat -c %h $1). See man stat for more format variables you can use. –  Joe Jan 3 '12 at 20:12
    
Best answer, by far. Kudos. –  MariusMatutiae Jun 27 at 8:24
ls -l

The first column will represent permissions. The second column will be the number of sub-items (for directories) or the number of paths to the same data (hard links, including the original file) to the file. Eg:

-rw-r--r--@    2    [username]    [group]    [timestamp]     HardLink
-rw-r--r--@    2    [username]    [group]    [timestamp]     Original
               ^ Number of hard links to the data
share|improve this answer
    
Helpful in determining IF a given file has [other] hard links, but not WHERE they are. –  mklement0 Feb 11 at 3:48

How about the following simpler one? (Latter might replace the long scripts above!)

If you have a specific file <THEFILENAME>and want to know all its hardlinks spread over the directory <TARGETDIR>, (which can even be the entire filesystem denoted by /)

find <TARGETDIR> -type f -samefile <THEFILENAME>

Extending the logic, if you want to know all the files in the <SOURCEDIR> having multiple hard-links spread over <TARGETDIR>:

find <SOURCEDIR> -type f -links +1 -printf "\n\n %n HardLinks of file : %H/%f \n" -exec find <TARGETDIR> -type f -samefile {} \;

share|improve this answer
    
This is for me the best answer! but i would not use -type f because the file can be a directory too. –  silvio Aug 30 '13 at 11:40
1  
@silvio: You can only create hard links to files, not directories. –  mklement0 Feb 11 at 3:45
    
@mklement0: You are right! –  silvio Feb 11 at 10:38
    
The . and .. entries in directories are hardlinks. You can tell how many subdirs are in a directory from the link count of .. This is moot anyway, since find -samefile . still won't print any subdir/.. output. find (at least the GNU version) seems to be hardcoded to ignore .., even with -noleaf. –  Peter Cordes Apr 21 at 18:53
    
also, that find-all-links idea is O(n^2), and runs find once for each member of a set of hardlinked files. find ... -printf '%16i %p\n' | sort -n | uniq -w 16 --all-repeated=separate would work, (16 isn't wide enough for a decimal representation of 2^63-1, so when your XFS filesystem is big enough to have inode numbers that high, watch out) –  Peter Cordes Apr 21 at 19:06

This is somewhat of a comment to Torocoro-Macho's own answer and script, but it obviously won't fit in the comment box.


Rewrote your script with more straightforward ways to find the info, and thus a lot less process invocations.

#!/bin/sh
xPATH=$(readlink -f -- "${1}")
for xFILE in "${xPATH}"/*; do
    [ -d "${xFILE}" ] && continue
    [ ! -r "${xFILE}" ] && printf '"%s" is not readable.\n' "${xFILE}" 1>&2 && continue
    nLINKS=$(stat -c%h "${xFILE}")
    if [ ${nLINKS} -gt 1 ]; then
        iNODE=$(stat -c%i "${xFILE}")
        xDEVICE=$(stat -c%m "${xFILE}")
        printf '\nItem: %s[%d] = %s\n' "${xDEVICE}" "${iNODE}" "${xFILE}";
        find "${xDEVICE}" -inum ${iNODE} -not -path "${xFILE}" -printf '     -> %p\n' 2>/dev/null
    fi
done

I tried to keep it as similar to yours as possible for easy comparison.

Comments on this script and yours

  • One should always avoid the $IFS magic if a glob suffices, since it is unnecessarily convoluted, and file names actually can contain newlines (but in practice mostly the first reason).

  • You should avoid manually parsing ls and such output as much as possible, since it will sooner or later bite you. For example: in your first awk line, you fail on all file names containing spaces.

  • printf will often save troubles in the end since it is so robust with the %s syntax. It also gives you full control over the output, and is consistent across all systems, unlike echo.

  • stat can save you a lot of logic in this case.

  • GNU find is powerful.

  • Your head and tail invocations could have been handled directly in awk with e.g. the exit command and/or selecting on the NR variable. This would save process invocations, which almost always betters performance severely in hard-working scripts.

  • Your egreps could just as well be just grep.

share|improve this answer
    
xDEVICE=$(stat -c%m "${xFILE}") does not work on all systems (for example: stat (GNU coreutils) 6.12). If the script outputs "Item: ?" at the front of each line, then replace this offending line with a line more like the original script, but with xITEM renamed to xFILE: xDEVICE=$(df "${xFILE}" | tail -1l | awk '{print $6}') –  kbulgrien Mar 28 '14 at 20:23
    
If you just want groups of hardlinks, rather than repeated with each member as the "master", use find ... -xdev -type f -links +1 -printf '%16i %p\n' | sort -n | uniq -w 16 --all-repeated=separate. This is MUCH faster, as it only traverses the fs once. For multiple FSes at once, you'd need to prefix the the inode numbers with a FS id. Maybe with find -exec stat... -printf ... –  Peter Cordes Apr 21 at 19:14
    
turned that idea into an answer –  Peter Cordes Apr 21 at 19:33

Based on the findhardlinks script (renamed it to hard-links), this is what I have refactored and made it work.

Output:

# ./hard-links /root

Item: /[10145] = /root/.profile
    -> /proc/907/sched
    -> /<some-where>/.profile

Item: /[10144] = /root/.tested
    -> /proc/907/limits
    -> /<some-where else>/.bashrc
    -> /root/.testlnk

Item: /[10144] = /root/.testlnk
    -> /proc/907/limits
    -> /<another-place else>/.bashrc
    -> /root/.tested

 

# cat ./hard-links
#!/bin/bash
oIFS="${IFS}"; IFS=$'\n';
xPATH="${1}";
xFILES="`ls -al ${xPATH}|egrep "^-"|awk '{print $9}'`";
for xFILE in ${xFILES[@]}; do
  xITEM="${xPATH}/${xFILE}";
  if [[ ! -r "${xITEM}" ]] ; then
    echo "Path: '${xITEM}' is not accessible! ";
  else
    nLINKS=$(ls -ld "${xITEM}" | awk '{print $2}')
    if [ ${nLINKS} -gt 1 ]; then
      iNODE=$(ls -id "${xITEM}" | awk '{print $1}' | head -1l)
      xDEVICE=$(df "${xITEM}" | tail -1l | awk '{print $6}')
      echo -e "\nItem: ${xDEVICE}[$iNODE] = ${xITEM}";
      find ${xDEVICE} -inum ${iNODE} 2>/dev/null|egrep -v "${xITEM}"|sed 's/^/   -> /';
    fi
  fi
done
IFS="${oIFS}"; echo "";
share|improve this answer
    
I posted comments on this script as a separate answer. –  Daniel Andersson Jun 13 '12 at 7:40

A GUI solution gets really close to your question:

You cannot list the actual hardlinked files from "ls" because, as previous commentators have pointed out, the file "names" are mere aliases to the same data. However, there actually is a GUI tool that gets really close to what you want which is to display a path listing of file names that point to the same data (as hardlinks) under linux, it is called FSLint. The option you want is under "Name clashes" -> deselect "checkbox $PATH" in Search (XX) -> and select "Aliases" from drop-down box after "for..." towards the top-middle.

FSLint is very poorly documented but I found that making sure the limited directory tree under "Search path" with the checkbox selected for "Recurse?" and the aforementioned options, a listing of hardlinked data with paths and names that "point" to the same data are produced after the program searches.

share|improve this answer
    
FSlint can be found at pixelbeat.org/fslint –  mklement0 Feb 11 at 3:43

There are a lot of answers with scripts to find all hardlinks in a filesystem. Most of them do silly things like running find to scan the whole filesystem for -samefile for EACH multiply-linked file. This is crazy; all you need is to sort on inode number and print duplicates.

find /filesystem-to-scan -xdev -type f -links +1 -printf '%16i %p\n' | sort -n | uniq -w 16 --all-repeated=separate

Using -type f -links +1 means that sort's input is only as big as the final output of uniq. Unless you run it on a subdirectory that only contains one of a set of hardlinks. Anyway, this will use a LOT less CPU time re-traversing the filesystem than any other posted solution.

sample output:

...
    17961006 /usr/bin/pkg-config.real
    17961006 /usr/bin/x86_64-pc-linux-gnu-pkg-config

    36646946 /usr/lib/i386-linux-gnu/dri/i915_dri.so
    36646946 /usr/lib/i386-linux-gnu/dri/i965_dri.so
    36646946 /usr/lib/i386-linux-gnu/dri/nouveau_vieux_dri.so
    36646946 /usr/lib/i386-linux-gnu/dri/r200_dri.so
    36646946 /usr/lib/i386-linux-gnu/dri/radeon_dri.so
...

TODO: un-pad the output. uniq has very limited field-selection support, so I pad the find output and use fixed-width.

And 16chars isn't wide enough for the maximum possible inode number (2^64-1 = 18446744073709551615). XFS chooses inode numbers based on where on disk they're allocated, not contiguously from 0, so large XFS filesystems can have >32bit inode numbers even if they don't have billions of files.

TODO: have find print a filesystem identifier so it doesn't find false positives with multiple directories specified on the find command-line (or so it can be used without -xdev). Maybe with -exec stat...something -printf? It doesn't have to be super-efficient, since it only needs to run for files that pass the -links +1 test.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.