あなたの質問に対する明確な答えはありません。シンボリックリンクとは異なり、ハードリンクは「元のファイル」と区別がつきません。
ディレクトリエントリは、ファイル名とinodeへのポインタで構成されています。inodeにはファイルのメタデータと実際のファイルの内容が含まれています。ハードリンクを作成すると、別のファイル名と同じinodeへの参照が作成されます。これらの参照は(少なくとも典型的なファイルシステムでは)一方向です – inodeは参照数を保持するだけです。どちらが「元の」ファイル名なのかを調べる方法は本質的にはありません。
ところで、ファイルを「削除」するためのシステムコールが unlink
と呼ばれるのはこのためです。これはハードリンクを削除しているだけです。
与えられたinodeへの他の参照を見つける唯一の方法は、どのファイルが問題のinodeを参照しているかを確認しながらファイルシステムを徹底的に検索することです。このチェックを行うには、シェルから'test A -ef B'を使うことができます。
ls -l
最初の列はパーミッションを表します。2番目の列は、サブ項目の数(ディレクトリの場合)や、そのファイルと同じデータ(元のファイルを含むハードリンク)へのパスの数を表します。例:
-rw-r--r--@ 2 [username] [group] [timestamp] HardLink
-rw-r--r--@ 2 [username] [group] [timestamp] Original
^ Number of hard links to the data
以下のようなシンプルなものはどうでしょうか?
find <TARGETDIR> -type f -samefile <THEFILENAME>
論理を拡張して、複数のハードリンクが<THEFILENAME>
に分散している<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 番号でソートして重複を表示することだけです。
ハードリンクされたファイルのすべてのセットを見つけてグループ化するためにファイルシステムを1回通過するだけで
find dirs -xdev \! -type d -links +1 -printf '%20D %20i %p\n' |
sort -n | uniq -w 42 --all-repeated=separate
ハードリンクされたファイルの複数セットを見つけるために、他の回答よりもはるかに高速です。
find /foo -samefile /bar
は、1 つのファイルだけを見つけるのに優れています。
-xdev
: 1つのファイルシステムに制限。! -type d
で uniq に FS-id も出力するので厳密には必要ありません。.
: リンクカウントを厳密に ..
-links +1
FS-id、inode番号、パスを表示する。> 1
数値ソートと一意性化を最初の42列で行い、空白行でグループを区切る -printf ...
を使用すると、ソートの入力は 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? uniq
はフィールド選択のサポートが非常に限られているので、私は検索出力にパッドを入れて固定幅を使用しています。20charsは最大可能なinodeまたはデバイス番号(2^64-1 = 18446744073709551615)のために十分な幅です。XFS は、ディスク上のどこに割り当てられたかに基づいて inode 番号を選択しますが、0 から連続しているわけではありませんので、大規模な XFS ファイルシステムでは、何十億ものファイルがなくても、32 ビット以上の inode 番号を持つことができます。他のファイルシステムでは、巨大なファイルでなくても、20桁のinode番号を持つかもしれません。
TODO: 重複ファイルのグループをパスでソートする。マウントポイントでソートしてから inode 番号でソートすると、ハードリンクがたくさんあるいくつかの異なるサブディレクトリを持っている場合に、物事が混ざってしまいます。(つまり、重複グループのグループは一緒になっているが、出力ではそれらが混ざってしまう)。
最後の sort -n | uniq ...
は、行のグループを一つのレコードとしてではなく、行を別々にソートします。改行のペアをNULバイトに変換する何かで前処理を行い、GNU ! -type d -links +1
を使用するとうまくいくかもしれません。awk
は単一の文字に対してのみ動作し、2->1 や 1->2 パターンでは動作しない。cut
ならそれができるだろう(あるいは、Perlやawkの中でパースしてソートするだけ)。uniq
も使えるかもしれません。
情報を見つけるためのよりわかりやすい方法であなたのスクリプトを書き直しました。
#!/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
マジックは常に避けるべきです。
ls
やそのような出力を手動で解析するのはできるだけ避けるべきです。例えば、最初の awk
行では、スペースを含むすべてのファイル名で失敗します。
printf
は %s
の構文が非常に堅牢なので、最終的にはトラブルを避けることができます。また、echo
とは異なり、出力を完全に制御することができ、すべてのシステムで一貫しています。
stat
は、この場合、多くのロジックを節約することができます。
GNU find
は強力です。
head
と tail
の呼び出しは、awk
コマンドや exit
変数の選択などで、NR
で直接処理することができます。これにより、プロセスの呼び出しが省かれますが、これはハードワークなスクリプトではほとんどの場合、パフォーマンスを著しく低下させることになります。
あなたのegrep
はただの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ソリューションは、あなたの質問に本当に近いものになります。
以前のコメンテーターが指摘したように、ファイルの「名前」は同じデータへの単なるエイリアスだからです。しかし、実際には、Linux上で同じデータを指す(ハードリンクとしての)ファイル名のパスリストを表示するGUIツールがあり、それはFSLintと呼ばれています。欲しいオプションは “Name clashes "の下の "Name clashes” -> Search (XX)の “checkbox $PATH "の選択を解除して、"for… "の後にあるドロップダウンボックスから "Aliases "を選択します。
FSLintのドキュメントは非常に貧弱ですが、「Search path」の下の限定されたディレクトリツリーを「Recurse?
ls
は「エイリアス」を使ってハードリンクをハイライトするように設定できますが、前に述べたようにハードリンクの「ソース」を表示する方法がないので、これを助けるために .hardlink
を追加しました。
.bashrc
alias ll='LC_COLLATE=C LS_COLORS="$LS_COLORS:mh=1;37" ls -lA --si --group-directories-first'
のどこかに以下を追加してください。