对于你的问题,其实并没有一个明确的答案。与符号链接不同,硬链接与 “原始文件 "是无法区分的。
目录条目由一个文件名和一个指向inode的指针组成。inode又包含文件元数据和(指向)实际文件内容的指针)。创建一个硬链接会创建另一个文件名+指向同一个inode的引用。这些引用是单向的(至少在典型的文件系统中)–inode只保留一个引用计数。没有内在的方法来找出哪个是 "原始 "文件名。
顺便说一下,这就是为什么系统调用 "删除 "一个文件叫做unlink
。它只是删除了一个硬链接。
只有当inode的引用数降到0时,才会删除inode和附加的数据。
要找到一个给定inode的其他引用,唯一的方法是在文件系统中彻底搜索,检查哪些文件引用了相关的inode。你可以在shell中使用'test A -ef B'来执行这个检查。
ls -l
第一列将代表权限。第二列将是子项目的数量(对于目录)或通往文件的同一数据(硬链接,包括原始文件)的路径数量。Eg:
-rw-r--r--@ 2 [username] [group] [timestamp] HardLink
-rw-r--r--@ 2 [username] [group] [timestamp] Original
^ Number of hard links to the data
``` 。
下面这个更简单的怎么样?后面可能会取代上面的长脚本!)
如果你有一个特定的文件<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 {} \;
有很多答案都是用脚本来查找文件系统中的所有硬链接。他们中的大多数人都会做一些愚蠢的事情,比如运行find来扫描整个文件系统中每个多重链接文件的-samefile
。这太疯狂了;你所需要的只是根据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
在前42列上进行数字排序和uniquify,用空行分隔组 使用sort -n | uniq ...
意味着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?: 用! -type d -links +1
或awk
来解除输出垫。cut
对字段选择的支持非常有限,所以我垫上查找输出,使用固定宽度。20chars的宽度足以满足最大可能的inode或设备号(2^64-1=18446744073709551615)。XFS根据它们在磁盘上分配的位置来选择inode号,而不是从0开始连续地选择,所以大型的XFS文件系统可以有>32位的inode号,即使它们没有数十亿个文件。其他文件系统即使不是巨大的,也可能有20位的inode号。
TODO: 按路径对重复的组进行排序。如果你有几个不同的子目录,其中有很多硬链接,那么按挂载点和inode号排序就会把事情混在一起。(也就是说,重复组的群组会在一起,但输出会把它们混在一起)。
最后的uniq
会分别对行进行排序,而不是将行的组作为一条记录。用一些东西进行预处理,将一对换行符转化为NUL字节,并使用GNU sort -k 3
可能会起到作用。sort --zero-terminated -k 3
只对单个字符进行操作,而不是2-/>1或1-/>2的模式。tr
可以做到(或者在perl或awk中进行解析和排序)。perl
也可能会成功。
重写了你的脚本,用更直接的方法来查找信息,因此减少了很多进程调用。
#!/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
我尽量让它和你的脚本相似,方便比较。
如果用 glob 就够了,应该总是避免使用 $IFS
魔法,因为它是不必要的复杂,而且文件名实际上可以包含换行符 (但实际上主要是第一个原因)。
你应该尽量避免手动解析ls
之类的输出,因为它迟早会咬你。例如:在你的第一行awk
中,你对所有包含空格的文件名都会失败。
-printf
往往会在最后省去麻烦,因为它与%s
语法是如此健壮。它还能让你完全控制输出,并且在所有系统中都是一致的,不像 echo
。
stat
在这种情况下可以为你节省很多逻辑。
GNU find
功能强大。
你的head
和tail
调用可以直接在awk
中用exit
命令和/或选择NR
变量来处理。这样可以节省进程调用,这对于工作繁忙的脚本来说,几乎总是能提高性能。
你的 egrep
s 也可以只是 grep
。
基于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 "";
```。
一个GUI解决方案非常接近你的问题。
你不能从 “ls "中列出真正的硬链接文件 因为正如前面的评论者指出的那样 文件名只是同一数据的别称 然而,实际上有一个GUI工具可以非常接近你的需求,那就是在linux下显示指向相同数据(作为硬链接)的文件名的路径列表,它叫做FSLint。你想要的选项是在 "名称冲突 "下 -/> 取消选择 "搜索(XX) "中的 "复选框$PATH” -/> 从 “for… "后的下拉框中选择 "别名"。
FSLint的文档很不完善,但我发现,确保 "搜索路径 "下的有限目录树,选中 "Recurse? "的复选框和上述选项,程序搜索后会产生一个路径和名称 "指向 "同一数据的硬链接数据列表。
你可以配置ls
使用 “别名 "来突出显示硬链接,但如前所述,没有办法显示硬链接的 "来源",这就是为什么我附加.hardlink
来帮助你的原因。
在你的.bashrc
alias ll='LC_COLLATE=C LS_COLORS="$LS_COLORS:mh=1;37" ls -lA --si --group-directories-first'
```中添加以下内容。