web-dev-qa-db-ja.com

重複ファイルを見つけてシンボリックリンクで置き換える

特定のディレクトリ内で重複したファイル(異なる名前であっても)をチェックし、それらを最初の出現を指すシンボリックリンクで置き換える方法を見つけようとしています。私はfdupesを試してみましたが、重複を一覧表示するだけです。
それがコンテキストです。アイコンのテーマを自分の好みに合わせてカスタマイズしています。親フォルダー内の名前や場所が異なり、さまざまな目的で使用されている場合でも、多くのアイコンが見つかりました。基本的に同じ画像です。同じ変更を20回または30回適用することは、1つだけが本当に必要な場合は冗長であるため、1つの画像のみを保持し、他のすべての画像にシンボリックリンクを設定します。

例として、testdirディレクトリ内でfdupes -r ./を実行すると、次の結果が返されることがあります。

./file1.png
./file2.png
./subdir1/anotherfile.png
./subdir1/subdir2/yetanotherfile.png

この出力を前提として、ファイルfile1.pngのみを保持し、他のすべてを削除して、元のファイル名をすべて維持しながら、それらを指すシンボリックリンクに置き換えます。したがって、file2.pngはその名前を保持しますが、重複するのではなくfile1.pngへのリンクになります。

これらのリンクは、絶対パスを指すのではなく、親のtestdirディレクトリーからの相対パスでなければなりません。つまり、yetanotherfile.png../../file1.pngではなく/home/testuser/.icons/testdir/file1.pngを指します。

GUIとCLIを含むソリューションの両方に興味があります。 fdupesの使用は必須ではありません。これは私が知っているツールであるため引用しましたが、他のツールを使用するソリューションにも対応できます。

これらすべてを処理するbashスクリプトを作成するのはそれほど難しくないはずですが、自分で書く方法を見つけるのに十分な専門知識はありません。

16
Sekhemty

最初;通常のハードリンクではなくシンボリックリンクを使用する必要がある理由はありますか?相対パスを持つシンボリックリンクの必要性を理解するのに苦労しています。これが私がこの問題をどのように解決するかです:

Debian(Ubuntu)バージョンのfdupesは-Lオプションを使用して重複をハードリンクに置き換えることができると思いますが、これを確認するためのDebianインストールがありません。

-Lオプション付きのバージョンがない場合は、 commandlinef で見つけたこの小さなbashスクリプトを使用できます。
この構文はbashでのみ機能することに注意してください。

fdupes -r -1 path | while read line; do master=""; for file in ${line[*]}; do if [ "x${master}" == "x" ]; then master=$file; else ln -f "${master}" "${file}"; fi; done; done

上記のコマンドは、「パス」内のすべての重複ファイルを見つけて、ハードリンクに置き換えます。これを確認するには、ls -ilRを実行してiノード番号を確認します。これは、10個の同一ファイルを持つサンプルです。

$ ls -ilR

total 20
3094308 -rw------- 1 username group  5 Sep 14 17:21 file
3094311 -rw------- 1 username group  5 Sep 14 17:21 file2
3094312 -rw------- 1 username group  5 Sep 14 17:21 file3
3094313 -rw------- 1 username group  5 Sep 14 17:21 file4
3094314 -rw------- 1 username group  5 Sep 14 17:21 file5
3094315 drwx------ 1 username group 48 Sep 14 17:22 subdirectory

./subdirectory:
total 20
3094316 -rw------- 1 username group 5 Sep 14 17:22 file
3094332 -rw------- 1 username group 5 Sep 14 17:22 file2
3094345 -rw------- 1 username group 5 Sep 14 17:22 file3
3094346 -rw------- 1 username group 5 Sep 14 17:22 file4
3094347 -rw------- 1 username group 5 Sep 14 17:22 file5

すべてのファイルには個別のiノード番号があり、個別のファイルになります。次に、それらを重複排除します。

$ fdupes -r -1 . | while read line; do j="0"; for file in ${line[*]}; do if [ "$j" == "0" ]; then j="1"; else ln -f ${line// .*/} $file; fi; done; done
$ ls -ilR
.:
total 20
3094308 -rw------- 10 username group  5 Sep 14 17:21 file
3094308 -rw------- 10 username group  5 Sep 14 17:21 file2
3094308 -rw------- 10 username group  5 Sep 14 17:21 file3
3094308 -rw------- 10 username group  5 Sep 14 17:21 file4
3094308 -rw------- 10 username group  5 Sep 14 17:21 file5
3094315 drwx------  1 username group 48 Sep 14 17:24 subdirectory

./subdirectory:
total 20
3094308 -rw------- 10 username group 5 Sep 14 17:21 file
3094308 -rw------- 10 username group 5 Sep 14 17:21 file2
3094308 -rw------- 10 username group 5 Sep 14 17:21 file3
3094308 -rw------- 10 username group 5 Sep 14 17:21 file4
3094308 -rw------- 10 username group 5 Sep 14 17:21 file5

これで、ファイルのiノード番号はすべて同じになりました。つまり、ファイルはすべてディスク上の同じ物理データを指しています。

これがあなたの問題を解決するか、少なくともあなたが正しい方向に向かっていることを願っています!

3
arnefm

スクリプティングをあまり好きではない場合は、 rdfind をお勧めします。指定されたディレクトリをスキャンして重複ファイルを探し、それらをハードリンクまたはソフトリンクします。私はこれを使用して、私のRuby gemsディレクトリの重複排除に大きな成功を収めました。Debian/ Ubuntuで利用可能です。

6
Andrew France

私も同様の状況でしたが、私の場合、シンボリックリンクは相対パスを指す必要があるため、トリックを行うために this python script と記述しました:

#!/usr/bin/env python
# Reads fdupes(-r -1) output and create relative symbolic links for each duplicate
# usage: fdupes -r1 . | ./lndupes.py

import os
from os.path import dirname, relpath, basename, join
import sys

lines = sys.stdin.readlines()

for line in lines:
    files = line.strip().split(' ')
    first = files[0]
    print "First: %s "% first
    for dup in files[1:]:
        rel = os.path.relpath(dirname(first), dirname(dup))
        print "Linking duplicate: %s to %s" % (dup, join(rel,basename(first)))
        os.unlink(dup)
        os.symlink(join(rel,basename(first)), dup)

各入力行(ファイルのリスト)に対して、スクリプトはファイルリストを(空白で区切られた)分割し、各ファイルから最初のファイルへの相対パスを取得して、シンボリックリンクを作成します。

4
filipenf

したがって、arnefmによって与えられた答え(インターネット全体にコピーされたもの)は、ファイル名のスペースを扱いません。ファイル内のスペースを処理するスクリプトを作成しました。

#!/bin/bash
fdupes -r -1 CHANGE_THIS_PATH | sed -e 's/\(\w\) /\1|/g' -e 's/|$//' > files
while read line; do
        IFS='|' read -a arr <<< "$line"
        orig=${arr[0]}
        for ((i = 1; i < ${#arr[@]}; i++)); do
                file="${arr[$i]}"
                ln -sf "$orig" "$file"
        done 
done < files

これが何をするかは、だれかを見つけ、それらを 'files'という名前のファイルに分けられたPIPEに書き込みます。

次に、ファイルを1行ずつ配列に読み込み、配列の各要素はPIPEで区切られます。

次に、配列の最初以外のすべての要素を反復処理し、ファイルを最初の要素へのシンボリックリンクに置き換えます。

Fdupesコマンドがサブシェルで実行される場合、外部ファイル( 'files')を削除できます。これは、whileによって直接読み取られますが、この方法はより明確に見えます。

1
David Ventura

事前の注意事項:

  • BASH固有
  • ファイル名にスペースがありません
  • 各行に最大2つのファイルが含まれていると想定します。

fdupes -1r common/base/dir | while read -r -a line ; do ln -sf $(realpath --relative-to ${line[1]} ${line[0]}) ${line[1]}; done

3つ以上のファイルが重複している場合(例:file1 file2 file3)、各ペアのシンボリックリンクを作成する必要があります-file1、file2およびfile1、file3を2つの別々のケースとして扱います。

if [[ ${#line[@]} -gt 2 ]] ;then 
  ln -sf $(realpath --relative-to ${line[1]} ${line[0]}) ${line[1]} 
  ln -sf $(realpath --relative-to ${line[2]} ${line[0]}) ${line[2]} 
  ...
fi

1行あたりの任意の数の重複を自動的に処理するためにこれを使用すると、もう少し手間がかかります。

別のアプローチは、最初に絶対パスへのシンボリックリンクを作成し、次にそれらを変換することです:

fdupes -1r /absolute/path/common/base/dir | while read -r -a line ; do ln -sf ${line[0]} ${line[1]}; done
chroot /absolute/path/common/base/dir ; symlinks -cr .

これは@Gillesの回答に基づいています: https://unix.stackexchange.com/a/100955/77319

0
Dani_l