ค้นหาไฟล์ที่ซ้ำกันและแทนที่ด้วย symlink


16

ฉันพยายามหาวิธีการตรวจสอบภายในไดเรกทอรีที่กำหนดสำหรับไฟล์ที่ซ้ำกัน (แม้จะมีชื่อที่แตกต่างกัน) และแทนที่ด้วย symlink ชี้ไปที่เกิดขึ้นครั้งแรก ฉันได้ลองแล้วfdupesแต่มันจะแสดงรายการที่ซ้ำกันเหล่านั้น
นั่นคือบริบท: ฉันกำหนดธีมไอคอนตามความชอบของฉันและฉันพบว่ามีไอคอนมากมายแม้ว่าพวกเขาจะมีชื่อและสถานที่แตกต่างกันภายในโฟลเดอร์หลักของพวกเขาและใช้เพื่อจุดประสงค์ที่แตกต่างกัน ภาพ. เนื่องจากการใช้การปรับเปลี่ยนแบบเดียวกันยี่สิบหรือสามสิบครั้งนั้นซ้ำซ้อนเมื่อจำเป็นเพียงภาพเดียวฉันต้องการเก็บเพียงภาพเดียวและเชื่อมโยงภาพอื่น ๆ ทั้งหมด

ตัวอย่างเช่นถ้าฉันทำงานfdupes -r ./ในไดเรกทอรีtestdirมันอาจกลับมาให้ฉันผลลัพธ์ต่อไปนี้:

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

เมื่อได้รับเอาต์พุตนี้ฉันต้องการเก็บเฉพาะไฟล์file1.pngลบส่วนที่เหลือทั้งหมดและแทนที่ด้วย symlink ที่ชี้ไปที่มันในขณะที่ยังคงชื่อไฟล์เดิมทั้งหมด ดังนั้นfile2.pngจะรักษาชื่อไว้ แต่จะกลายเป็นลิงก์ไปสู่file1.pngแทนที่จะเป็นข้อมูลซ้ำ

ลิงก์เหล่านั้นไม่ควรชี้ไปที่พา ธ สัมบูรณ์ แต่ควรสัมพันธ์กับtestdirไดเร็กทอรีพาเรนต์ คือyetanotherfile.pngจะชี้ไปที่../../file1.pngไม่/home/testuser/.icons/testdir/file1.png

ฉันสนใจทั้งในโซลูชันที่เกี่ยวข้องกับ GUI และ CLI ไม่จำเป็นต้องใช้fdupesฉันได้อ้างถึงเพราะเป็นเครื่องมือที่ฉันรู้ แต่ฉันเปิดรับโซลูชันที่ใช้เครื่องมืออื่นเช่นกัน

ฉันค่อนข้างแน่ใจว่าสคริปต์ทุบตีในการจัดการทั้งหมดนี้ไม่ควรยากที่จะสร้าง แต่ฉันไม่เชี่ยวชาญพอที่จะหาวิธีเขียนด้วยตนเอง

คำตอบ:


3

ครั้งแรก; มีเหตุผลที่คุณต้องใช้ symlinks ไม่ใช่ hardlinks ปกติหรือไม่? ฉันมีเวลายากที่จะเข้าใจความจำเป็นในการเชื่อมโยงกับเส้นทางที่สัมพันธ์กัน นี่คือวิธีที่ฉันจะแก้ปัญหานี้:

ฉันคิดว่า fdupes รุ่น Debian (Ubuntu) สามารถแทนที่รายการซ้ำด้วยฮาร์ดลิงก์โดยใช้-Lตัวเลือก แต่ฉันไม่มีการติดตั้ง Debian เพื่อตรวจสอบสิ่งนี้

หากคุณไม่ได้มีรุ่นที่มี-Lตัวเลือกที่คุณสามารถใช้สคริปต์ทุบตีเล็ก ๆ ที่ฉันพบในcommandlinefu
โปรดทราบว่าไวยากรณ์นี้จะทำงานในทุบตีเท่านั้น

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และดูที่หมายเลขไอโหนด นี่คือ samle ที่มีสิบไฟล์เหมือนกัน:

$ 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

ไฟล์ทั้งหมดมีหมายเลขไอโหนดแยกกันทำให้ไฟล์แยกกัน ตอนนี้ให้ลดการซ้ำซ้อน:

$ 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

ตอนนี้ไฟล์ทั้งหมดมีหมายเลขไอโหนดเดียวกันซึ่งหมายความว่าไฟล์ทั้งหมดชี้ไปที่ข้อมูลฟิสิคัลเดียวกันบนดิสก์

ฉันหวังว่านี่จะช่วยแก้ปัญหาของคุณหรืออย่างน้อยก็ชี้ให้คุณในทิศทางที่ถูกต้อง!


ฉันจำได้ว่าfdupesมีตัวเลือกเพื่อแทนที่ dupes ด้วยลิงก์ @arnefm แต่ฉันไม่เห็นอะไรเลยในผู้ชายและไม่เป็นตัวเลือกในv1.51(Ubuntu 14.04.2 LTS)
Alastair

My fork jdupesat github.com/jbruchon/jdupesมี-Lตัวเลือกซึ่งจะทำการเชื่อมโยงฮาร์ดที่ต้องการของชุดที่ซ้ำกัน
โจดี้ลีบรูชอน

ฉันเพิ่ง tweaked สคริปต์ที่นี่ มันจะไม่จัดการช่องว่าง แต่จะจัดการอักขระพิเศษอื่น ๆ (ฉันมีสตริงการสืบค้น URL ในไฟล์) นอกจากนี้${line//…/}ส่วนที่ใช้งานไม่ได้สำหรับฉันดังนั้นฉันจึงได้วิธีที่สะอาดกว่าในการรับไฟล์ "master" แรกไปยัง hardlink
IBBoard

1
เราจะต้องใช้ซอฟต์ลิงค์สัมพัทธ์หรือไม่ถ้าเราใช้rsyncระบบไฟล์ประเภทอื่น หรือถ้าระบบไฟล์ไม่รักษาลำดับชั้นเช่นมันเป็นเซิร์ฟเวอร์สำรองที่ทำให้ทุกอย่างภายใต้/«machine-name»/...? หรือถ้าคุณต้องการกู้คืนจากการสำรองข้อมูล ฉันไม่เห็นว่าจะมีการเก็บรักษาลิงก์ไว้ที่นี่อย่างไร ซอฟต์ลิงก์สัมพัทธ์จะมีโอกาสรอดชีวิตที่ดีกว่าฉันอาจคิดว่า
บัดดี้

6

หากคุณไม่ชอบสคริปต์มากนักฉันสามารถแนะนำrdfindได้ ซึ่งจะสแกนไดเรกทอรีที่ให้ไว้เพื่อหาไฟล์ที่ซ้ำกันและฮาร์ดลิงก์หรือซอฟต์ลิงค์ด้วยกัน ฉันใช้มันเพื่อขจัดความซ้ำซ้อนของไดเรกทอรี Ruby gem ของฉันด้วยความสำเร็จที่ยิ่งใหญ่ มันมีอยู่ใน Debian / Ubuntu


4

ฉันมีสถานการณ์ที่คล้ายกัน แต่ในกรณีของฉันลิงก์สัญลักษณ์ควรชี้ไปที่เส้นทางสัมพัทธ์ดังนั้นฉันจึงเขียนสคริปต์ของหลามนี้เพื่อทำการหลอกลวง:

#!/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)

สำหรับแต่ละบรรทัดอินพุต (ซึ่งเป็นรายการไฟล์) สคริปต์จะแยกรายการไฟล์ (คั่นด้วยช่องว่าง) ให้พา ธ สัมพันธ์จากแต่ละไฟล์เป็นไฟล์แรกจากนั้นสร้าง symlink


1

ดังนั้นคำตอบที่ได้รับจาก 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

สิ่งนี้ทำคือค้นหาสิ่งที่ซ้ำซ้อนและเขียน PIPE แยกเป็นไฟล์ที่ชื่อว่า 'files'

จากนั้นมันจะอ่านไฟล์ย้อนกลับทีละบรรทัดเข้าไปในอาร์เรย์และองค์ประกอบของอาร์เรย์แต่ละรายการจะถูกคั่นด้วย PIPE

จากนั้นจะวนซ้ำองค์ประกอบที่ไม่ใช่ของอาร์เรย์ทั้งหมดแทนที่ไฟล์ด้วย symlink ไปที่องค์ประกอบแรก

ไฟล์ภายนอก ('files') สามารถลบได้หากคำสั่ง fdupes ถูกดำเนินการใน subshell นั่นจะถูกอ่านโดยตรงในขณะที่ แต่วิธีนี้ดูเหมือนชัดเจน


2
รุ่นนี้จัดการกับไฟล์ที่มีชื่อที่มีไพพ์หรือไม่? ฉันสมมติว่าทั้งสองเวอร์ชันไม่จัดการชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่ แต่นั่นเป็นข้อ จำกัด ของ fdupes มากกว่าสิ่งอื่นใด
dhag

มันไม่ได้ แต่คุณสามารถตั้งค่า IFS เป็นสิ่งที่คุณต้องการ (เช่นแก้ไขค่าในการแทนที่ sed) จากนั้นคุณไม่ควรมีปัญหาใด ๆ (IFS เป็น 'ñ' หรืออะไรทำนองนั้นที่ใช้ได้)
David Ventura

สิ่งนี้สร้าง symlink ที่เสียหายและฉันมีไฟล์ที่เชื่อมโยงกับตัวเอง ห้ามใช้งาน
MrMesees

0

คำเตือนบางอย่างอยู่ด้านหน้า:

  • ทุบตีเฉพาะ
  • ไม่มีที่ว่างในชื่อไฟล์
  • สมมติว่าแต่ละบรรทัดมี 2 ไฟล์มากที่สุด

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

หากไฟล์มากกว่า 2 ไฟล์ซ้ำกัน (เช่น file1 file2 file3) มากกว่าที่เราต้องการสร้าง symlink สำหรับแต่ละคู่ - ปฏิบัติกับ 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

การใช้ค่านี้เพื่อจัดการจำนวนรายการซ้ำโดยอัตโนมัติต่อบรรทัดจะใช้ความพยายามเพิ่มขึ้นเล็กน้อย

อีกวิธีหนึ่งคือการสร้าง symlink เป็นเส้นทางที่สมบูรณ์ก่อนจากนั้นแปลงเป็น:

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: /unix//a/100955/77319

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.