Home » Questions » Computers [ Ask a new question ]

How can you see the actual hard link by ls?

How can you see the actual hard link by ls?

I run

Asked by: Guest | Views: 257
Total answers/comments: 5
bert [Entry]

"You can find inode number for your file with

ls -i

and

ls -l

shows references count (number of hardlinks to a 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 (.)"
bert [Entry]

"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"
bert [Entry]

"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.

With only one pass over the filesystem to find and group all sets of hardlinked files

find dirs -xdev \! -type d -links +1 -printf '%20D %20i %p\n' |
sort -n | uniq -w 42 --all-repeated=separate

This is much faster than the other answers for finding multiple sets of hardlinked files.
find /foo -samefile /bar is excellent for just one file.

-xdev : limit to one filesystem. Not strictly needed since we also print the FS-id to uniq on
! -type d reject directories: the . and .. entries mean they're always linked.
-links +1 : link count strictly > 1
-printf ... print FS-id, inode number, and path. (With padding to fixed column widths that we can tell uniq about.)
sort -n | uniq ... numeric sort and uniquify on the first 42 columns, separating groups with a blank line

Using ! -type d -links +1 means that sort's input is only as big as the final output of uniq so we aren't doing a huge amount of string sorting. 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:

...
2429 76732484 /home/peter/weird-filenames/test/.hiddendir/foo bar
2429 76732484 /home/peter/weird-filenames/test.orig/.hiddendir/foo bar

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

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

TODO?: un-pad the output with awk or cut. uniq has very limited field-selection support, so I pad the find output and use fixed-width. 20chars is wide enough for the maximum possible inode or device 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. Other filesystems might have 20-digit inode numbers even if they aren't gigantic.

TODO: sort groups of duplicates by path. Having them sorted by mount point then inode number mixes things together, if you have a couple different subdirs that have lots of hardlinks. (i.e. groups of dup-groups go together, but the output mixes them up).

A final sort -k 3 would sort lines separately, not groups of lines as a single record. Preprocessing with something to transform a pair of newlines into a NUL byte, and using GNU sort --zero-terminated -k 3 might do the trick. tr only operates on single characters, not 2->1 or 1->2 patterns, though. perl would do it (or just parse and sort within perl or awk). sed might also work."
bert [Entry]

"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."
bert [Entry]

"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 """";"