ディレクトリ内の重複を見つけて削除する


12

複数のimgファイルがあるディレクトリがあり、それらのいくつかは同一ですが、それらはすべて異なる名前を持っています。重複を削除する必要がありますが、bashスクリプトのみで外部ツールは必要ありません。私はLinuxの初心者です。md5合計を比較するためにネストされたforループを試し、結果に応じて削除しましたが、構文に何か問題があり、機能しません。何か助け?

私が試したのは...

for i in directory_path; do
    sum1='find $i -type f -iname "*.jpg" -exec md5sum '{}' \;'
    for j in directory_path; do
        sum2='find $j -type f -iname "*.jpg" -exec md5sum '{}' \;'
        if test $sum1=$sum2 ; then rm $j ; fi
    done
done

私は得る: test: too many arguments


質問にはエラーメッセージも含めてください。
テルドン

fdupesなどの外部ツールを使用できないのはなぜですか?@terdonの答えは驚くべきものですが、良いツールを使用することが可能な場合に進むべき方法である理由を強調しています。何らかの専用のハードウェアまたはサーバーである場合、fdupesなどのツールが利用可能なマシンからネットワークなどを介してアクセスできる可能性があります。
ジョー

回答:


28

スクリプトにはかなりの数の問題があります。

  • 最初に、コマンドの結果を変数に割り当てるために、変数をbacktics(`command`)またはできればで囲む必要があります$(command)'command'コマンドの結果を変数に割り当てる代わりに、コマンド自体を文字列として割り当てる単一引用符()で囲みます。したがって、test実際には:

    $ echo "test $sum1=$sum2"
    test find $i -type f -iname "*.jpg" -exec md5sum {} \;=find $j -type f -iname "*.jpg" -exec md5sum {} \;
  • 次の問題は、コマンドmd5sumが単なるハッシュ以上のものを返すことです。

    $ md5sum /etc/fstab
    46f065563c9e88143fa6fb4d3e42a252  /etc/fstab

    最初のフィールドのみを比較したいのでmd5sum、最初のフィールドのみを出力するコマンドに渡すことで出力を解析する必要があります。

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | cut -f 1 -d ' '

    または

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}' 
  • また、findコマンドは1つだけではなく多くの一致を返し、それらの一致のそれぞれが2番目の一致によって複製されfindます。いくつかの点であなたは自分自身に同じファイルを比較されることを、この手段は、md5sumと同じになりますし、あなたが削除することになりますすべてのファイルを(私が含むテストディレクトリでこれを実行a.jpgしてb.jpg):

    for i in $(find . -iname "*.jpg"); do
      for j in $(find . -iname "*.jpg"); do
         echo "i is: $i and j is: $j"
      done
    done   
    i is: ./a.jpg and j is: ./a.jpg   ## BAD, will delete a.jpg
    i is: ./a.jpg and j is: ./b.jpg
    i is: ./b.jpg and j is: ./a.jpg
    i is: ./b.jpg and j is: ./b.jpg   ## BAD will delete b.jpg
  • for i in directory_pathディレクトリの配列を渡さない限り、実行したくないでしょう。これらのファイルがすべて同じディレクトリにある場合は、for i in $(find directory_path -iname "*.jpg")を実行してすべてのファイルを調べます。

  • 悪いアイデアで使用するfor検索の出力とループを。whileループまたはグロビングを使用する必要があります。

    find . -iname "*.jpg" | while read i; do [...] ; done

    または、すべてのファイルが同じディレクトリにある場合:

    for i in *jpg; do [...]; done

    シェルと設定したオプションに応じて、サブディレクトリ内のファイルに対してもグロビングを使用できますが、ここでは説明しません。

  • 最後に、変数も引用符で囲む必要があります。そうしないと、スペースを含むディレクトリパスがスクリプトを中断します。

ファイル名にはスペース、改行、バックスラッシュ、その他の奇妙な文字を含めることができwhileます。ループ内でそれらを正しく処理するには、さらにオプションを追加する必要があります。あなたが書きたいのは次のようなものです:

find dir_path -type f -iname "*.jpg" -print0 | while IFS= read -r -d '' i; do
  find dir_path -type f -iname "*.jpg" -print0 | while IFS= read -r -d '' j; do
    if [ "$i" != "$j" ]
    then
      sum1=$(md5sum "$i" | cut -f 1 -d ' ' )
      sum2=$(md5sum "$j" | cut -f 1 -d ' ' )
      [ "$sum1" = "$sum2" ] && rm "$j"
    fi
  done
done

さらに簡単な方法は次のとおりです。

find directory_path -name "*.jpg" -exec md5sum '{}' + | 
 perl -ane '$k{$F[0]}++; system("rm $F[1]") if $k{$F[0]}>1'

ファイル名のスペースを処理できるより良いバージョン:

find directory_path -name "*.jpg" -exec md5sum '{}' + | 
 perl -ane '$k{$F[0]}++; system("rm \"@F[1 .. $#F]\"") if $k{$F[0]}>1'

この小さなPerlスクリプトは、findコマンドの結果(md5sumとファイル名)を実行します。-a以下のためのオプションperlの空白で分割入力ラインとはに保存されますFので、配列$F[0]のmd5sumとなり$F[1]、ファイル名を指定します。md5sumはハッシュに保存され、kスクリプトはハッシュが既に表示されているかどうかを確認し(if $k{$F[0]}>1)、ハッシュが表示されている場合はファイルを削除します(system("rm $F[1]"))。


それは機能しますが、大規模な画像コレクションでは非常に遅くなり、保持するファイルを選択できません。これをよりエレガントな方法で処理する多くのプログラムがあります。


Perlスニペットの場合は+1。本当にエレガント!呼び出しunlinkを行う代わりに、Perl独自のものを使用することもできsystemます。
ジョセフR.

@JosephR。ありがとう:)。ただし、バグがあった場合、スペースを含むファイル名では失敗します。これは、名前の最初のスペースまでの最初の文字のみがにあるためです$F[1]。配列スライスを使用して修正しました。unlink()については知っていますが、perlismsを最小限に抑え、Perlを知らない方がシステムコールを理解しやすいようにしたかったのです。
テルドン

13

fdupesプロセス全体を単純化し、ユーザーに重複を削除するように求める、気の利いたプログラムがあります。私はそれをチェックする価値があると思う:

$ fdupes --delete DIRECTORY_WITH_DUPLICATES
[1] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz        
[2] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz.1

Set 1 of 1, preserve files [1 - 2, all]: 1

   [+] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz
   [-] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz.1

基本的には、どのファイルのために私を促し続ける私はタイプされた、1を、それが第二を削除しました。

他の興味深いオプションは次のとおりです。

-r --recurse
    for every directory given follow subdirectories encountered within

-N --noprompt
    when used together with --delete, preserve the first file in each set of duplicates and delete the others without prompting the user

あなたの例から、おそらく次のように実行したいでしょう:

fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES

man fdupes利用可能なすべてのオプションを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.