Perl
Bash
awk

fdupesを自作してみた

More than 3 years have passed since last update.

ファイルの重複チェックに便利なfdupesコマンドですが、入ってない環境でも使えるようにワンライナーを書いてみました。車輪の再発明です。

find . -type f | xargs md5sum > /tmp/a; grep -f <(awk '{print $1}' /tmp/a | sort | uniq -c | awk '{if($1 > 1) print $2}') /tmp/a | sort | perl -lane 'push @{$a{$F[0]}}, $F[1]; END {print join "\n", @{$a{$_}}, "" for keys %a}'



fdupesとは

同一ファイルを探してくれるコマンドです。

重複したファイルがゴミじゃないか?とか、共通化出来ないか?とか、シンボリックリンクに置き換えられないか?などの調査の取っ掛かりに使えます。

# fdupes -r .

./images/milestone_done.png
./images/task_done.png

./images/arrow_collapsed.png
./images/bullet_arrow_right.png

./help/bg/wiki_syntax_detailed.html
./help/pl/wiki_syntax_detailed.html
./help/sv/wiki_syntax_detailed.html
./help/bs/wiki_syntax_detailed.html
...

空行ごとにグルーピングされたもの同士でdiff取ってみれば、それぞれすべて同じということが分かります。

# diff ./images/milestone_done.png ./images/task_done.png

# diff ./images/arrow_collapsed.png ./images/bullet_arrow_right.png
# diff ./help/bg/wiki_syntax_detailed.html ./help/pl/wiki_syntax_detailed.html
# diff ./help/pl/wiki_syntax_detailed.html ./help/sv/wiki_syntax_detailed.html


md5sum

これを自作するには、findでカレント配下の一覧を取りつつ、md5sumで各ファイルのフィンガープリントを計算します。やりたいことは、これが同じものをグルーピングさせて表示させたい、というだけです。

# find . -type f | xargs md5sum

12203dd0b04ea085217b93b0871053b5 ./dispatch.fcgi.example
61affd115c8d129c258713445d558301 ./javascripts/datepicker.js
50e838e3ddd98dc7845db32539fe980f ./javascripts/attachments.js
25e711a474b72694444e46441a45daef ./javascripts/i18n/datepicker-hr.js
4ec013ff08274062b9affe005d6e0bad ./javascripts/i18n/datepicker-it.js
a67ea4ea3a1305230d78c663d781aec5 ./javascripts/i18n/datepicker-cs.js
0a30262822d33944bef4f32f2c40dabd ./javascripts/i18n/datepicker-nl.js
d379d734a97928692297893bf538c503 ./javascripts/i18n/datepicker-eu.js
01d3088ea6837a13205cea656d042b2c ./javascripts/i18n/datepicker-fa.js
5fca3c9b96c5ef5142521f7e650edf5f ./javascripts/i18n/datepicker-th.js
...

左側のフィンガープリントだけを抽出し、uniq -cで数を数えます。(わかりやすくするため、数字順で逆ソートもしてます)

# find . -type f | xargs md5sum | awk '{print $1}' | sort | uniq -c | sort -nr

43 8ec587ffbce2b400b7a3abc1087c61f2
41 150d3ca182103800e898533daac8a93c
6 c49e65f5c02ababbaa0c9a9968424950
2 980dcfdb816ce626e4d2df5c2a308549
2 40c58172e0c52eee4deb5227ec37f0cf
1 ff96c02d8f18116bb3f005f2c8b86e91
1 feb0aa630b9a43ca8b40ca6c85038327
1 fd6a3f960f0d6e1ce7e6466d592453fe
1 fba036d7348ff28f7ab7dacbc52bc32a
1 fb00cc94dfc8f319dda707b957aae8a6
...

カウント数が2以上のものを抽出し、フィンガープリントだけ表示させます。

# find . -type f | xargs md5sum | awk '{print $1}' | sort | uniq -c | awk '{if($1>1) print $2}'

150d3ca182103800e898533daac8a93c
40c58172e0c52eee4deb5227ec37f0cf
8ec587ffbce2b400b7a3abc1087c61f2
980dcfdb816ce626e4d2df5c2a308549
c49e65f5c02ababbaa0c9a9968424950

これを一旦ファイルに落とし、grep -fの材料とします。

# find . -type f | xargs md5sum | awk '{print $1}' | sort | uniq -c | awk '{if($1>1) print $2}' > /tmp/g

元の一覧からgrep -fします。

# find . -type f | xargs md5sum | grep -f /tmp/g

c49e65f5c02ababbaa0c9a9968424950 ./javascripts/jstoolbar/lang/jstoolbar-en.js
c49e65f5c02ababbaa0c9a9968424950 ./javascripts/jstoolbar/lang/jstoolbar-sq.js
c49e65f5c02ababbaa0c9a9968424950 ./javascripts/jstoolbar/lang/jstoolbar-he.js
c49e65f5c02ababbaa0c9a9968424950 ./javascripts/jstoolbar/lang/jstoolbar-uk.js
c49e65f5c02ababbaa0c9a9968424950 ./javascripts/jstoolbar/lang/jstoolbar-en-gb.js
c49e65f5c02ababbaa0c9a9968424950 ./javascripts/jstoolbar/lang/jstoolbar-az.js
150d3ca182103800e898533daac8a93c ./help/bg/wiki_syntax.html
8ec587ffbce2b400b7a3abc1087c61f2 ./help/bg/wiki_syntax_detailed.html
150d3ca182103800e898533daac8a93c ./help/sl/wiki_syntax.html
8ec587ffbce2b400b7a3abc1087c61f2 ./help/sl/wiki_syntax_detailed.html
...

ソートして同一フィンガープリント同士でグルーピングします。

# find . -type f | xargs md5sum | grep -f /tmp/g | sort | perl -MData::Dumper -lane 'push @{$a{$F[0]}}, $F[1]; END {print Dumper %a}'

$VAR1 = '150d3ca182103800e898533daac8a93c';
$VAR2 = [
'./help/ar/wiki_syntax.html',
'./help/az/wiki_syntax.html',
'./help/bg/wiki_syntax.html',
'./help/bs/wiki_syntax.html',
'./help/ca/wiki_syntax.html',
'./help/da/wiki_syntax.html',
'./help/el/wiki_syntax.html',
'./help/en-gb/wiki_syntax.html',
(中略)
'./help/zh/wiki_syntax.html'
];
$VAR3 = 'c49e65f5c02ababbaa0c9a9968424950';
$VAR4 = [
'./javascripts/jstoolbar/lang/jstoolbar-az.js',
'./javascripts/jstoolbar/lang/jstoolbar-en-gb.js',
'./javascripts/jstoolbar/lang/jstoolbar-en.js',
'./javascripts/jstoolbar/lang/jstoolbar-he.js',
'./javascripts/jstoolbar/lang/jstoolbar-sq.js',
'./javascripts/jstoolbar/lang/jstoolbar-uk.js'
];
$VAR5 = '8ec587ffbce2b400b7a3abc1087c61f2';
$VAR6 = [
'./help/ar/wiki_syntax_detailed.html',
'./help/az/wiki_syntax_detailed.html',
...

グループごとに空行で区切るように表示を工夫すればほぼ完成です。

# find . -type f | xargs md5sum | grep -f /tmp/g | sort | perl -lane 'push @{$a{$F[0]}}, $F[1]; END {print join "\n", @{$a{$_}}, "" for keys %a}'

./help/ar/wiki_syntax.html
./help/az/wiki_syntax.html
./help/bg/wiki_syntax.html
./help/bs/wiki_syntax.html
./help/ca/wiki_syntax.html
./help/da/wiki_syntax.html
./help/el/wiki_syntax.html
./help/en-gb/wiki_syntax.html
(中略)
./help/vi/wiki_syntax.html
./help/zh/wiki_syntax.html

./javascripts/jstoolbar/lang/jstoolbar-az.js
./javascripts/jstoolbar/lang/jstoolbar-en-gb.js
./javascripts/jstoolbar/lang/jstoolbar-en.js
./javascripts/jstoolbar/lang/jstoolbar-he.js
./javascripts/jstoolbar/lang/jstoolbar-sq.js
./javascripts/jstoolbar/lang/jstoolbar-uk.js

./help/ar/wiki_syntax_detailed.html
./help/az/wiki_syntax_detailed.html
...


リファクタリング

bashの<()を使ってgrep -f用の中間ファイルを消します。

# find . -type f | xargs md5sum | grep -f <(find . -type f | xargs md5sum | awk '{print $1}' | sort | uniq -c | awk '{if($1 > 1) print $2}') | sort | perl -lane 'push @{$a{$F[0]}}, $F[1]; END {print join "\n", @{$a{$_}}, "" for keys %a}'

find . -type f | xargs md5sumが重複しているので中間ファイルとして切り出します。(やむなく)

# find . -type f | xargs md5sum > /tmp/a; grep -f <(awk '{print $1}' /tmp/a | sort | uniq -c | awk '{if($1 > 1) print $2}') /tmp/a | sort | perl -lane 'push @{$a{$F[0]}}, $F[1]; END {print join "\n", @{$a{$_}}, "" for keys %a}'

なんとか中間ファイルを作らない方法を試行錯誤しましたが、残念ながら力及ばず。これにて完成とします。

なかなか楽しいエクササイズになりました。