web-dev-qa-db-ja.com

lsによる実際のハードリンクはどうやって見ることができますか?

走る

ln /a/A /b/B

ファイルAがaで指しているフォルダlsを見てみたい。

あなたのファイルのinode番号を見つけることができます

ls -i

そして

ls -l

参照カウント(特定のiノードへのハードリンク数)を表示します

iノード番号が見つかったら、同じiノードを持つすべてのファイルを検索できます。

find . -inum NUM

現在のディレクトリ(。)内のinode NUMのファイル名を表示します。

163
zzr

あなたの質問に対する明確な答えは実際にはありません。シンボリックリンクとは異なり、ハードリンクは「元のファイル」と区別がつきません。

ディレクトリエントリは、ファイル名とiノードへのポインタで構成されています。 iノードには、ファイルのメタデータと(実際のファイルの内容へのポインタ)が含まれています。ハードリンクを作成すると、別のファイル名+同じiノードへの参照が作成されます。これらの参照は単方向です(少なくとも典型的なファイルシステムでは) - iノードは参照カウントを保持するだけです。 「元の」ファイル名がどれであるかを見つけるための固有の方法はありません。

ところで、ファイルを "削除する"ためのシステムコールがunlinkと呼ばれるのはこのためです。ハードリンクを削除するだけです。 iノードの参照カウントが0になった場合にのみ、添付データのiノードが削除されます。

特定のiノードへの他の参照を見つける唯一の方法は、問題のiノードを参照しているファイルを確認しながら、ファイルシステムを徹底的に検索することです。このチェックを実行するには、シェルから 'test A -ef B'を使用します。

60

UNIXにはハードリンクとシンボリックリンクがあります(それぞれ"ln""ln -s"で作られています)。シンボリックリンクは、単に別のファイルへの実際のパスを含み、ファイルシステムを横断することができるファイルです。

ハードリンクはUNIXの初期の頃からありました(とにかく覚えていることができます、そしてそれはかなりしばらく前に戻ってきています)。これらは、exactと同じ基本データを参照する2つのディレクトリエントリです。ファイル内のデータは、そのinodeによって指定されます。ファイルシステム上の各ファイルはiノードを指していますが、各ファイルが固有のiノードを指している必要はありません。ここからハードリンクが発生します。

Iノードは特定のファイルシステムに対してのみ一意であるため、ハードリンクは同じファイルシステム上になければならないという制限があります(シンボリックリンクとは異なり)。シンボリックリンクとは異なり、特権ファイルはありません。それらはすべて同等です。データ領域はallそのinodeを使用しているファイルが削除されたときにのみ解放されます(そしてすべてのプロセスがそれを閉じますが、これは別の問題です)。

特定のファイルのiノードを取得するには、"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
33
user53528
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
24
eyelidlessness

次の簡単なものはどうですか。 (後に、上記の長いスクリプトが置き換えられる可能性があります。)

特定のファイル<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 {} \; 
11

ファイルシステム内のすべてのハードリンクを見つけるためのスクリプトに関する回答はたくさんあります。それらのほとんどは、ファイルシステム全体を-samefileでスキャンしてEACH多重リンクファイルを探すようなばかげたことをします。狂ってる;必要なのは、iノード番号をソートして重複を印刷することだけです。

find directories.. -xdev ! -type d -links +1 -printf '%20D %20i %p\n' | sort -n | uniq -w 42 --all-repeated=separate(私のオリジナルのコマンドを微調整して、FS-id(%D)をサポートし、通常のファイルだけでなくすべてのディレクトリではないファイルタイプを処理できるようにしてくれた。@Tinoに感謝します。)

! -type d -links +1を使うことは、sortの入力がuniqの最終出力と同じくらい大きいことを意味します。ハードリンクのセットの1つだけを含むサブディレクトリでそれを実行しない限り。とにかく、これは他の投稿されたソリューションよりもファイルシステムを再トラバースするLOTより少ない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は非常に限られたフィールド選択のサポートを持っているので、私はfind出力を埋め込み、固定幅を使います。 20文字は、可能な最大iノード番号またはデバイス番号(2 ^ 64-1 = 18446744073709551615)に十分な幅です。 XFSは、ディスク上の割り当て位置に基づいて、0から連続していないiノード番号を選択するため、大規模なXFSファイルシステムでは、数十億のファイルがなくても32ビットを超えるiノード番号を使用できます。他のファイルシステムは、たとえ巨大でなくても、20桁のiノード番号を持つことがあります。

TODO:パスごとに重複グループをソートします。マウントポイントでソートしてから、inode番号を使うと、ハードリンクがたくさんあるサブディレクトリがいくつかある場合は、混在させることができます。 (すなわち、重複グループのグループは一緒になるが、出力はそれらを混同する)。

最後のsort -k 3は、行のグループを単一のレコードとしてではなく、行を個別にソートします。改行のペアをNULバイトに変換するための何かを前処理し、GNU sort --zero-terminated -k 3を使用することでうまくいく可能性があります。ただし、trは1文字のみで動作し、2 - > 1または1 - > 2パターンではありません。 Perlはそれをするでしょう(あるいは単にPerlやawkの中でパースしソートするでしょう)。 sedも動作するかもしれません。

4
Peter Cordes

これは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

私は比較を容易にするためにできるだけそれをあなたのものに似せておくようにしました。

このスクリプトとあなたのコメントについてのコメント

  • Globが十分であれば、$IFSマジックを常に避けるべきです。なぜなら、globは不必要に複雑になり、ファイル名は実際には改行を含むことができるからです(しかし実際には主に最初の理由)。

  • lsとそのような出力を手動で解析することはできるだけ遅らせるべきです。遅かれ早かれ噛み付くでしょう。たとえば、最初のawk行では、スペースを含むすべてのファイル名に失敗します。

  • printf%s構文と非常に堅牢であるため、結局のところ問題を解決するでしょう。また、echoとは異なり、出力を完全に制御でき、すべてのシステムで一貫しています。

  • この場合statはあなたに多くのロジックを節約することができます。

  • GNU findは強力です。

  • あなたのheadtailの呼び出しはawkの中で直接処理されたかもしれません。 exitコマンドおよび/またはNR変数の選択これはプロセス呼び出しを節約するでしょう、そしてそれはほとんどいつも勤勉なスクリプトでパフォーマンスを著しく向上させます。

  • あなたのegrepはちょうどgrepでも構いません。

3

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 "";
2
Torocoro-Macho

GUIソリューションは本当にあなたの質問に近づきます:

以前のコメンテーターが指摘したように、ファイル "names"は同じデータへの単なるエイリアスであるため、 "ls"から実際にハードリンクされたファイルをリストすることはできません。しかし、実際にはLinuxツールの下で同じデータを(ハードリンクとして)指すファイル名のパスリストを表示することが目的のものに本当に近づくGUIツールがあります。これはFSLintと呼ばれます。必要なオプションは、検索(XX) - > [名前の衝突] - > [チェックボックス$ PATH]の選択を解除し、[for ...]の後の上部中央に向かって[Aliases]を選択します。

FSLintはあまり文書化されていませんが、[検索パス]の下の限定されたディレクトリツリーで[再帰]チェックボックスがオンになっていることを確認しました。前述のオプションでは、プログラムが検索した後に、同じデータを「指す」パスと名前を持つハードリンクされたデータのリストが作成されます。

1
Charles

'alias'を使ってハードリンクを強調表示するようにlsを設定することができますが、前述のようにハードリンクの 'source'を表示する方法がないので、それを助けるために.hardlinkを追加します。

highlight hardlinks

.bashrcのどこかに以下を追加してください

alias ll='LC_COLLATE=C LS_COLORS="$LS_COLORS:mh=1;37" ls -lA --si --group-directories-first'
1