日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

inode番号が重複するファイルを表示(解説編)

inode番号が重複するファイルを表示 - 日々之迷歩、初心者置いてけぼりでしたので解説をば。

動作環境は、Mac OSX Yosemite。HomebrewでGNU grep(ggrep)を追加でインストール。インストールは下記を参考に。

高速化したGNU grepをインストールする - Qiita

下記のように、基本操作の組み合わせで考えてみると比較的簡単。

  • /usr/bin以下の重複するinode番号をリストアップ
  • ls -i /usr/binの結果から、リストアップされたinode番号が含まれる行を抽出

重複に関連する操作の基本はsortからのuniq、基本じゃ。探したいキーワードを含む行の抽出はgrep、基本じゃ。この基本の組み合わせを一時ファイル無しでワンライナーする場合、<(コマンド)記法が必要になってくる(kshbashzsh限定)。

それでは実際に解いていく。まずは重複するinode番号をリストアップしてみる。ls -i /usr/binで、inode番号とコマンドファイル名を出力。(行が多いのでheadで最初の10行だけ表示)

$ \ls -i /usr/bin | head
 8569390 2to3
 8569390 2to3-
 8569393 2to3-2.7
 8569394 2to32.6
15236776 BuildStrings
15236795 CpMac
15236803 DeRez
15236821 GetFileInfo
15236887 MergePef
15236890 MvMac

ここからsort -k1,1でinode番号をキーにして整列。-k1,1は1カラム目で並べ替えるという指定。

$ \ls -i /usr/bin | sort -k1,1 | head
 1723714 escputil
 1724489 cups-calibrate
 1744470 oauth
 1744531 prettify_json.rb
 1744587 edit_json.rb
 1744661 update_rubygems
 8203291 ipmitool
 8204825 ditto
 8209715 scp
 8209716 sftp

整列した結果について、awk '{print $1}'で、1カラム目のinode番号のみ出力。

$ \ls -i /usr/bin | sort -k1,1 | awk '{print $1}' | head
1723714
1724489
1744470
1744531
1744587
1744661
8203291
8204825
8209715
8209716

ここから重複する行のみを抽出すればよい。まずはuniq -cで実際に重複している数を確認してみる。1カラム目が重複している数になる。

見やすくするためsort -rn -k1,1で重複が多い順に並べ替えて確認。38個も重複してるのがあるな・・

$ \ls -i /usr/bin | sort -k1,1 | awk '{print $1}' | uniq -c | sort -rn -k1,1 | head
  38 8557661
  35 8546497
  15 8578607
   6 8551790
   5 8569390
   4 15236764
   3 8541772
   3 8486486
   3 18734005
   3 18730932

さて、重複しているものがあることがわかった。ちょっと戻って、uniq -dで重複している行のみを抽出。-dオプションは重複する行のみを表示。これで後半の検索で利用するキーワードが準備完了。

$ \ls -i /usr/bin | sort -k1,1 | awk '{print $1}' | uniq -d | head
8214826
8486486
8541772
8546497
8551346
8551790
8555174
8555191
8555195
8557011

この結果を一時ファイルに保存。更にls -i /usr/binも一時ファイルに保存。そしてgrep -fオプションで、検索キーワードが書かれたファイルを指定し、検索するファイルを指定。結果を見やすくsortする。lessとmore、grep兄弟等がinode番号が同じことが分かる。

$ \ls -i /usr/bin | sort -k1,1 | awk '{print $1}' | uniq -d > temp1
$ \ls -i /usr/bin > temp2
$ grep -f temp1 temp2 | sort -k1,1 | head
 8214826 less
 8214826 more
 8486486 egrep
 8486486 fgrep
 8486486 grep
 8541772 groups
 8541772 id
 8541772 whoami
 8546497 binhex.pl
 8546497 crc32
$ rm temp1 temp2

さて、これで目的は果たしたのだが、一時ファイルを作らなければいけないのがちょっとイケてない。そこで<(コマンド)記法が役に立つ。kshbashzsh限定だが、コマンドの実行結果をファイルとして指定出来る。

ちょっとした例を。lsの出力とls -aの出力の差分を取ってみる。こんな感じで便利な機能だと思う。

$ diff <(ls) <(ls -a) | head
0a1,48
> ./
> ../
> .CFUserTextEncoding
> .DS_Store
> .FontForge/
> .Rapp.history
> .Trash/
> .Xauthority
> .atom/

さて、<(コマンド)記法をふまえて、改めてワンライナーを考えてみる。ggrep -f -と指定すると、検索キーワードを標準入力から受け取ることが出来る。(grepじゃなくてggrepなのは、Mac標準のgrepBSD系で、grep -f -の指定が出来なかったため)

さあ、これで最終回答じゃ!

$ \ls -i /usr/bin | sort -k1,1 | awk '{print $1}' | uniq -d | ggrep -f - <(\ls -i /usr/bin) | sort -k1,1 | head
 8214826 less
 8214826 more
 8486486 egrep
 8486486 fgrep
 8486486 grep
 8541772 groups
 8541772 id
 8541772 whoami
 8546497 binhex.pl
 8546497 crc3

さて、いかがだっただろうか?シェル芸の基本がギュッと詰まっているのではないかと思う。さあ、これであなたもシェル芸人!

<<<追記>>>

MacBSDgrepでも、一時ファイルを作らずワンライナ書けた。難しく考えすぎてたかも・・・<(コマンド)の記法がたくさん出てくると見にくい気もするが。

$ grep -f <(\ls -i /usr/bin | sort -k1,1 | awk '{print $1}' | uniq -d) <(\ls -i /usr/bin) | sort -k1,1 | head
 8214826 less
 8214826 more
 8486486 egrep
 8486486 fgrep
 8486486 grep
 8541772 groups
 8541772 id
 8541772 whoami
 8546497 binhex.pl
 8546497 crc32