web-dev-qa-db-ja.com

同じディレクトリ内の重複ファイルを見つけて削除するにはどうすればよいですか?

ディレクトリ内で重複したファイルを見つけ、スペースを解放するために1つを残して他をすべて削除したい。シェルスクリプトを使用してこれをどのように実現しますか?

例えば:

pwd
folder

その中のファイルは次のとおりです。

log.bkp
log
extract.bkp
extract

Log.bkpを他のすべてのファイルと比較する必要があります。(内容によって)重複ファイルが見つかった場合は、削除する必要があります。同様に、ファイル「log」は、その後に続く他のすべてのファイルでチェックする必要があります。

これまでのところ、これを書きましたが、望ましい結果が得られていません。

#!/usr/bin/env ksh
count=`ls -ltrh /folder | grep '^-'|wc -l`
for i in `/folder/*`
do
   for (( j=i+1; j<=count; j++ ))
   do
      echo "Current two files are $i and $j"
      sdiff -s $i  $j
      if [ `echo $?` -eq  0 ]
      then
         echo "Contents of $i and $j are same"
       fi
    done
 done
6
Su_scriptingbee

コマンドラインツールを使用するだけで十分で、シェルスクリプトを作成する必要がない場合は、ほとんどのディストリビューションでfdupesプログラムを使用してこれを行うことができます。

同じ機能を持つGUIベースのfslintツールもあります。

9
einonm

このソリューションは、O(n)時間内に重複を検出します。各ファイルにはチェックサムが生成されており、各ファイルは連想配列を介して既知のチェックサムのセットと比較されます。

#!/bin/bash
#
# Usage:  ./delete-duplicates.sh  [<files...>]
#
declare -A filecksums

# No args, use files in current directory
test 0 -eq $# && set -- *

for file in "$@"
do
    # Files only (also no symlinks)
    [[ -f "$file" ]] && [[ ! -h "$file" ]] || continue

    # Generate the checksum
    cksum=$(cksum <"$file" | tr ' ' _)

    # Have we already got this one?
    if [[ -n "${filecksums[$cksum]}" ]] && [[ "${filecksums[$cksum]}" != "$file" ]]
    then
        echo "Found '$file' is a duplicate of '${filecksums[$cksum]}'" >&2
        echo rm -f "$file"
    else
        filecksums[$cksum]="$file"
    fi
done

コマンドラインでファイル(またはワイルドカード)を指定しない場合、現在のディレクトリにあるファイルのセットが使用されます。複数のディレクトリにあるファイルを比較しますが、ディレクトリ自体に再帰するようには書かれていません。

セットの「最初の」ファイルは常に最終的なバージョンと見なされます。ファイルの時間、権限、所有権は考慮されません。内容のみが考慮されます。

rm -f "$file"の行からechoを削除して、意図したとおりに動作することを確認します。その行をln -f "${filecksums[$cksum]}" "$file"に置き換えると、コンテンツをハードリンクできることに注意してください。同じようにディスク容量を節約できますが、ファイル名を失うことはありません。

8
roaima

スクリプトの主な問題は、iが単なる数値であるのに対し、jは実際のファイル名を値として取るということです。名前を配列に取り、インデックスとしてijの両方を使用すると機能します。

_files=(*)
count=${#files[@]}
for (( i=0 ; i < count ;i++ )); do 
    for (( j=i+1 ; j < count ; j++ )); do
        if diff -q "${files[i]}" "${files[j]}"  >/dev/null ; then
            echo "${files[i]} and ${files[j]} are the same"
        fi
    done
done
_

(Bashとksh/_ksh93_ Debianで動作するようです。)

割り当てa=(this that)は、配列aを2つの要素thisおよびthat(インデックス0および1)で初期化します。ワードスプリットとグロビングは通常どおりに機能するため、files=(*)filesを現在のディレクトリ(dotfilesを除く)内のすべてのファイルの名前で初期化します。 _"${files[@]}"_は配列のすべての要素に展開され、ハッシュ記号は長さを要求するため、_${#files[@]}_は配列内の要素の数です。 (_${files}_は配列の最初の要素であり、_${#files}_は配列ではなく最初の要素の長さです!)

_for i in `/folder/*`
_

ここのバッククォートは確かにタイプミスですか?最初のファイルをコマンドとして実行し、残りを引数として渡します。

2
ilkkachu

これを行うツールがあり、それをより効率的に行います。動作しているときのソリューションはO(n²)です。つまり、実行にかかる時間はn²に比例します。ここで、nは問題のサイズで、ファイルの合計バイト数です。最良のアルゴリズムは、O(n)の近くでこれを行うことができます。 (Aはbig-O表記について説明しています。これは、アルゴリズムの効率を要約する方法です。)

最初に、各ファイルのハッシュを作成し、それらを比較するだけです。ほとんど同じである大きなファイルがたくさんある場合、これは多くの時間を節約します。

次に、ショートカット方法を使用します。ファイルのサイズが異なる場合、それらは同じではありません。同じサイズの別のファイルがない限り、開かないでください。

1
ctrl-alt-delor

この短い解決策についてどう思いますか:

for file in *; do
    find . -type f ! -name "$file" -exec sh -c ' cmp -s '"$file"' {} && echo rm {} ' \;
done

cmp は2つのファイルをバイト単位で比較し、両方のファイルが同じ場合は何も報告せず、異なる場合はエラーを報告します。 -sはサイレント結果に使用されます。

echoを削除して重複ファイルの削除を実行するか、mv -t /path/to/check/again {}を使用して重複ファイルを他のディレクトリに移動して最初に再確認します。

テストする:

==> file1 <==
this is a $
file1$

==> file2 <==
this is a file2$

==> file3 <==
this is file1 a$

==> file4 <==
thisisafile1$

==> filex <==
this is a $
file1$

==> filey <==
this is a file2$

結果:

$ ls
file1  file2  file3  file4
0
αғsнιη

ちなみに、チェックサムまたはハッシュを使用することは良い考えです。私のスクリプトはそれを使用していません。しかし、ファイルが小さく、ファイルの量が大きくない場合(10〜20ファイルなど)、このスクリプトは非常に高速に動作します。 100個以上のファイルがある場合、各ファイルで1000行になると、時間は10秒以上になります。

使用法:./duplicate_removing.sh files/*

#!/bin/bash

for target_file in "$@"; do
    shift
    for candidate_file in "$@"; do
        compare=$(diff -q "$target_file" "$candidate_file")
        if [ -z "$compare" ]; then
            echo the "$target_file" is a copy "$candidate_file"
            echo rm -v "$candidate_file"
        fi
    done
done

テスト中

ランダムファイルの作成:./creating_random_files.sh

#!/bin/bash

file_amount=10
files_dir="files"

mkdir -p "$files_dir"

while ((file_amount)); do
    content=$(shuf -i 1-1000)
    echo "$RANDOM" "$content" | tee "${files_dir}/${file_amount}".txt{,.copied} > /dev/null
    ((file_amount--))
done

実行./duplicate_removing.sh files/*そして出力を取得

the files/10.txt is a copy files/10.txt.copied
rm -v files/10.txt.copied
the files/1.txt is a copy files/1.txt.copied
rm -v files/1.txt.copied
the files/2.txt is a copy files/2.txt.copied
rm -v files/2.txt.copied
the files/3.txt is a copy files/3.txt.copied
rm -v files/3.txt.copied
the files/4.txt is a copy files/4.txt.copied
rm -v files/4.txt.copied
the files/5.txt is a copy files/5.txt.copied
rm -v files/5.txt.copied
the files/6.txt is a copy files/6.txt.copied
rm -v files/6.txt.copied
the files/7.txt is a copy files/7.txt.copied
rm -v files/7.txt.copied
the files/8.txt is a copy files/8.txt.copied
rm -v files/8.txt.copied
the files/9.txt is a copy files/9.txt.copied
rm -v files/9.txt.copied
0
MiniMax