您如何通过ls看到实际的硬链接?


97

我跑

ln /a/A /b/B

我想a在文件A指向by的文件夹中看到ls


1
硬链接不是指针,符号链接是指针。它们是同一文件(inode)的多个名称。在进行link(2)系统调用之后,没有任何意义是原始的而链接是链接。正如答案所指出的,这就是为什么查找所有链接的唯一方法是find / -samefile /a/A。因为一个索引节点的一个目录条目不会“知道”同一索引节点的其他目录条目。他们所做的只是引用索引节点,以便当其姓氏为时可以将其删除unlink(2)ed。(这是ls输出中的“链接计数” )。
彼得·科德斯

@PeterCordes:引用计数是否实际存储在硬链接条目中?这就是您的措辞所隐含的含义(“它们所做的就是refcount inode ...”),但是如果链接之间彼此不了解,那将是没有意义的,因为当一个链接更新时,所有其他链接都必须以某种方式被更新。还是引用计数存储在索引节点本身中?(请原谅我,如果这是一个愚蠢的问题,我认为自己是新手,而且我还在学习)。
loneboat

1
refcount存储在inode中,正如您从其他事实中最终确定的那样,它确实是这种情况。:)目录条目被命名为指向inode的指针。当您有多个名称指向同一个inode时,我们将其称为“硬链接”。
彼得·科德斯

Answers:


171

您可以使用以下命令找到文件的索引节点号

ls -i

ls -l

显示引用计数(到特定inode的硬链接数)

找到索引节点编号后,可以搜索具有相同索引节点的所有文件:

find . -inum NUM

将在当前目录(。)中显示inode NUM的文件名


46
您可以运行find。-samefile文件名
BeowulfNode42

1
@ BeowulfNode42这个命令很棒,但是它至少需要相同文件的共享根文件夹。
Itachi 2016年

1
这个答案给出了一个务实的“做到这一点”,但我强烈认为@LaurenceGonsalves回答了“如何”和/或“为什么”的问题。
Trevor Boyd Smith

65

您的问题确实没有明确的答案。与符号链接不同,硬链接与“原始文件”没有区别。

目录条目由文件名和指向索引节点的指针组成。索引节点又包含文件元数据和(指向实际文件内容的指针)。创建硬链接会创建另一个文件名+对相同inode的引用。这些引用是单向的(至少在典型的文件系统中)-索引节点仅保留引用计数。没有固有的方法可以找出哪个是“原始”文件名。

顺便说一下,这就是为什么系统调用“删除”文件的原因unlink。它只是删除一个硬链接。仅当索引节点的引用计数降至0时,索引节点的附加数据才会被删除。

查找对给定索引节点的其他引用的唯一方法是彻底搜索文件系统,检查哪些文件引用了相关索引节点。您可以从外壳程序中使用“ test A -ef B”来执行此检查。


35
这意味着不存在到另一个文件的硬链接,因为原始文件也是硬链接。硬链接指向磁盘上位置
jtbandes

12
@jtbandes:硬链接指向一个指向实际数据的索引节点。
dash17291 2013年

33

UNIX具有硬链接和符号链接(分别由"ln"和制成"ln -s")。符号链接只是一个文件,其中包含另一个文件的真实路径,并且可以跨文件系统。

硬链接自UNIX成立以来就存在(无论如何,我仍然记得,这已经有一段时间了)。它们是两个引用完全相同的基础数据的目录条目。文件中的数据由其指定inode。文件系统上的每个文件都指向一个索引节点,但不要求每个文件都指向一个唯一的索引节点-这就是硬链接的来源。

由于索引节点仅对于给定的文件系统是唯一的,因此存在一个限制,即硬链接必须位于同一文件系统上(与符号链接不同)。请注意,与符号链接不同,没有特权文件-它们都是相等的。仅当删除使用该inode的所有文件时(所有进程也将其关闭),才释放数据区域。

您可以使用该"ls -i"命令获取特定文件的索引节点。然后,您可以使用该"find <filesystemroot> -inum <inode>"命令查找具有给定inode的文件系统上的所有文件。

这是一个脚本,正是这样做的。您可以通过以下方式调用它:

findhardlinks ~/jquery.js

它将找到该文件系统上的所有文件,这些文件是该文件的硬链接:

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

这是脚本。

#!/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

@pax:脚本中似乎有一个错误。我从. ./findhardlinks.bash在OS X的Zsh中开始。屏幕上我当前的窗口关闭。

4
@Masi这是您的首要问题。(与源命令相同)。这将导致exit 1命令退出您的shell。使用chmod a + x findhardlinks.bash,然后使用./findhardlinks.bash执行它,或使用bash findhardlinks.bash
njsf,2009年


3
要以编程方式做到这一点,如果你用这个代替它可能更有弹性:INUM=$(stat -c %i $1)。也NUM_LINKS=$(stat -c %h $1)。请参阅man stat以获取更多可以使用的格式变量。
2012年

到目前为止,最佳答案。荣誉
MariusMatutiae

24
ls -l

第一列将代表权限。第二列将是子项目数(用于目录)或文件的相同数据(硬链接,包括原始文件)的路径数。例如:

-rw-r--r--@    2    [username]    [group]    [timestamp]     HardLink
-rw-r--r--@    2    [username]    [group]    [timestamp]     Original
               ^ Number of hard links to the data

2
有助于确定给定文件是否具有[其他]硬链接,但不知道它们在哪里。
mklement0

另外,硬链接和原始文件之间没有技术上的区别。它们都是相同的,因为它们只是指向inode光盘内容。
Guyarad

13

接下来的简单的怎么样?(稍后可能会替换上面的长脚本!)

如果您有一个特定的文件,<THEFILENAME>并且想知道其所有硬链接遍布目录<TARGETDIR>(甚至可以是表示的整个文件系统/

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

扩展逻辑,如果您想知道<SOURCEDIR>多个硬链接分布在其中的所有文件<TARGETDIR>

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

这对我来说是最好的答案!但我不会使用,-type f因为文件也可以是目录。
silvio

3
@silvio:您只能创建指向文件的硬链接,而不能创建目录。
mklement0

@ mklement0:你是对的!
silvio 2015年

目录中的...条目是硬链接。您可以根据的链接计数来判断目录中有多少个子目录.。无论如何这find -samefile .都没有意义,因为仍然不会打印任何subdir/..输出。 find(至少是GNU版本)..,即使使用,也似乎被硬编码忽略-noleaf
彼得·科德斯

同样,“查找所有链接”的想法是O(n^2),并且find对一组硬链接文件的每个成员运行一次。 find ... -printf '%16i %p\n' | sort -n | uniq -w 16 --all-repeated=separate会起作用,(16的宽度不足以表示2 ^ 63-1的十进制表示,因此,当您的XFS文件系统足够大以使inode数很高时,请
当心

5

脚本有很多答案,可以找到文件系统中的所有硬链接。他们中的大多数人都做一些愚蠢的事情,例如运行find来扫描整个文件系统以-samefile查找EACH多重链接文件。这太疯狂了; 您所需要做的就是对inode号进行排序并打印重复项。

只需一次通过文件系统即可查找和分组所有硬链接文件集

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

这比查找多组硬链接文件的其他答案要快得多。
find /foo -samefile /bar仅需一个文件就非常好。

  • -xdev:限制为一个文件系统。由于我们还将FS-id打印到uniq上,因此并非严格需要
  • ! -type d拒绝目录:...条目表示它们始终处于链接状态。
  • -links +1 :链接数严格 > 1
  • -printf ...显示FS-id,inode编号和路径。(通过填充到固定的列宽,我们可以知道uniq。)
  • sort -n | uniq ... 在前42列上进行数字排序和唯一化,用空行分隔组

使用! -type d -links +1表示sort的输入仅与uniq的最终输出一样大,因此我们不会进行大量的字符串排序。除非您在仅包含一组硬链接之一的子目录上运行它。无论如何,与其他发布的解决方案相比,重新遍历文件系统所花费的CPU时间要少很多。

样本输出:

...
            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 ?:用awk或取消输出的填充cutuniq对字段选择的支持非常有限,因此我填充了find输出并使用了固定宽度。20个字符的宽度足以容纳最大可能的inode或设备编号(2 ^ 64-1 = 18446744073709551615)。XFS根据在磁盘上分配的位置来选择索引节点号,而不是从0开始连续地选择索引索引号,因此,大型XFS文件系统即使没有数十亿个文件,也可以具有大于32位的索引节点号。其他文件系统可能不具有20位的inode编号,即使它们不是巨大的。

TODO:按路径对重复项进行排序。如果您有几个带有大量硬链接的不同子目录,则按挂载点对它们进行排序,然后按索引号将它们混合在一起。(即dup-group组在一起,但是输出将它们混合在一起)。

final sort -k 3将单独对行进行排序,而不是将行组作为单个记录进行排序。进行一些预处理以将一对换行符转换为NUL字节,然后使用GNU sort --zero-terminated -k 3可能会成功。 tr但是,仅可用于单个字符,而不能用于2-> 1或1-> 2模式。 perl会做到这一点(或者只是在perl或awk中解析和排序)。 sed可能也可以。


1
%D是文件系统标识符(这是当前引导独特而没有文件系统被umountED),所以下面是更通用的:find directories.. -xdev ! -type d -links +1 -printf '%20i %20D %p\n' | sort -n | uniq -w 42 --all-repeated=separate。只要没有给定目录在文件系统级别上包含另一个目录,该方法就起作用,并且它查看可以进行硬链接的所有内容(例如设备或软链接-是的,软链接的链接数可以大于1)。请注意,dev_tino_t为64位长的今天。只要我们拥有64位系统,这可能就会成立。
蒂诺2015年

@Tino:关于使用using ! -type d而不是的重点-type f。通过组织一些文件集合,我什至在我的文件系统上还有一些硬链接的符号链接。用改进的版本更新了我的答案(但我先把fs-id放在了首位,所以排序顺序至少是按文件系统分组。)
Peter Cordes

3

这是对Torocoro-Macho自己的答案和脚本的某种评论,但显然不适合在评论框中。


用更直接的方式重写脚本来查找信息,从而减少了流程调用。

#!/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

我试图使其尽可能与您相似,以便于比较。

有关此脚本和您的脚本的评论

  • $IFS如果一个glob足够,就应该始终避免使用魔术,因为它不必要地盘绕,并且文件名实际上可以包含换行符(但实际上多数是第一个原因)。

  • 您应该避免手动解析ls和此类输出,因为它迟早会咬住您。例如:在第一awk行中,所有包含空格的文件名都失败。

  • printf最终会节省麻烦,因为它的%s语法非常强大。与相比,它还使您可以完全控制输出,并且在所有系统中都保持一致echo

  • stat 在这种情况下可以节省很多逻辑。

  • GNU find 是强大的。

  • head和的tail调用可以直接awk通过exit命令和/或在NR变量上进行选择来处理。这将节省进程调用,这几乎总是可以在辛苦的脚本中大大提高性能。

  • egrep的也可能只是grep


xDEVICE = $(stat -c%m“ $ {xFILE}”)不适用于所有系统(例如:stat(GNU coreutils)6.12)。如果脚本输出“ Item:?” 在每行的开头,然后用更像原始脚本的行替换此有问题的行,但将xITEM重命名为xFILE:xDEVICE = $(df“ $ {xFILE}” | tail -1l | awk'{print $ 6} ')
kbulgrien 2014年

如果您只需要硬链接组,而不是将每个成员都作为“主”重复,请使用find ... -xdev -type f -links +1 -printf '%16i %p\n' | sort -n | uniq -w 16 --all-repeated=separate。这要快得多,因为它仅遍历fs一次。对于一次多个FS,您需要在inode编号前面加上FS ID。也许和find -exec stat... -printf ...
Peter Cordes

将这个想法变成了答案
Peter Cordes

2

基于findhardlinks脚本(将其重命名为hard-links),这就是我重构并使其起作用的内容。

输出:

# ./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 "";

我对此脚本发表了评论,作为一个单独的答案。
Daniel Andersson 2012年

1

GUI解决方案非常接近您的问题:

您无法从“ ls”中列出实际的硬链接文件,因为如先前的评论员所指出的那样,文件“名称”仅是相同数据的别名。但是,实际上有一个GUI工具非常接近您想要的,即显示文件名的路径列表,该文件名指向Linux下的相同数据(如硬链接),称为FSLint。您想要的选项位于“名称冲突”下->在搜索(XX)中取消选择“复选框$ PATH”->并从下拉框中选择“别名”,然后指向顶部。

FSLint的文档记录很少,但是我发现要确保“搜索路径”下的受限目录树已选中“递归?”复选框。以及上述选项,在程序搜索后,将产生具有“指向”相同数据的路径和名称的硬链接数据列表。



1

您可以配置ls为使用“别名”突出显示硬链接,但是如前所述,没有办法显示硬链接的“源”,这就是我在后面附加.hardlink帮助的原因。

突出显示硬链接

将以下内容添加到您的 .bashrc

alias ll='LC_COLLATE=C LS_COLORS="$LS_COLORS:mh=1;37" ls -lA --si --group-directories-first'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.