テスト

テスト

ディレクトリ内の重複ファイルを検索し、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

答え1

単にコマンドライン ツールを使用するだけでよく、シェル スクリプトを作成する必要がない場合は、fdupesほとんどのディストリビューションでこの操作を実行するためのプログラムが利用可能です。

fslint同じ機能を備えたGUI ベースのツールもあります。

答え2

このソリューションは、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

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

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

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

答え3

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

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/ ksh93Debian では動作するようです。)

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

for i in `/folder/*`

ここでのバックティックは間違いなくタイプミスでしょうか? 最初のファイルをコマンドとして実行し、残りをその引数として渡すことになります。

答え4

ちなみに、チェックサムやハッシュを使用するのは良い考えです。私のスクリプトでは使用していません。ただし、ファイルが小さく、ファイルの数が多くない場合 (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

関連情報