จะลบไฟล์ที่ซ้ำกันในไดเรกทอรีได้อย่างไร?


25

ฉันดาวน์โหลดรูปภาพจำนวนมากในไดเรกทอรี
ดาวน์โหลดเปลี่ยนชื่อไฟล์ที่มีอยู่แล้ว
ฉันยังเปลี่ยนชื่อไฟล์บางไฟล์ด้วยตนเอง

a.jpg
b.jpg
b(2).jpg
hello.jpg      <-- manually renamed `b(3).jpg`
c.jpg
c(2).jpg
world.jpg      <-- manually renamed `d.jpg`
d(2).jpg
d(3).jpg

วิธีลบรายการที่ซ้ำกันออก ผลลัพธ์ควรเป็น:

a.jpg
b.jpg
c.jpg
world.jpg

หมายเหตุ: ชื่อไม่สำคัญ ฉันแค่ต้องการไฟล์ uniq

คำตอบ:


27

ทุบตี 4.x

#!/bin/bash
declare -A arr
shopt -s globstar

for file in **; do
  [[ -f "$file" ]] || continue

  read cksm _ < <(md5sum "$file")
  if ((arr[$cksm]++)); then 
    echo "rm $file"
  fi
done

นี่คือทั้งเรียกซ้ำและจัดการชื่อไฟล์ใด ๆ ข้อเสียคือต้องใช้เวอร์ชัน 4.x สำหรับความสามารถในการใช้อาเรย์แบบเชื่อมโยงและการค้นหาแบบเรียกซ้ำ ลบechoถ้าคุณชอบผลลัพธ์

รุ่น gawk

gawk '
  {
    cmd="md5sum " q FILENAME q
    cmd | getline cksm
    close(cmd)
    sub(/ .*$/,"",cksm)
    if(a[cksm]++){
      cmd="echo rm " q FILENAME q
      system(cmd)
      close(cmd)
    }
    nextfile
  }' q='"' *

โปรดทราบว่าสิ่งนี้จะยังคงอยู่ในไฟล์ที่มีเครื่องหมายคำพูดคู่ในชื่อของพวกเขา ไม่มีทางที่จะไปawkไหนด้วย ลบechoถ้าคุณชอบผลลัพธ์


ใช่รุ่นทุบตีทำงานได้สำหรับฉัน แต่ในการทดสอบของฉันมี 2 โฟลเดอร์ที่คล้ายกันมันลบครึ่งหนึ่งของรายการที่ซ้ำกันในโฟลเดอร์หนึ่งและอีกครึ่งหนึ่งในอีกโฟลเดอร์หนึ่ง ทำไม. ฉันคาดว่าจะลบทุกคน (ซ้ำ) ในโฟลเดอร์เดียว
Ferroao

@Ferroao บางทีพวกเขาอาจจะไม่ซ้ำกันแน่นอน ถ้าเพียงหนึ่งบิตปิด md5 แฮชที่สคริปต์ของฉันใช้เพื่อตรวจสอบความซ้ำซ้อนจะแตกต่างกันโดยสิ้นเชิง คุณสามารถเพิ่มecho cksmบรรทัดถัดจากบรรทัดที่ขึ้นต้นด้วยreadถ้าคุณต้องการดูแฮชของแต่ละไฟล์
SiegeX

ไม่ "สำเนา" ทั้งหมด (สำเนา) ถูกลบเหลืออีก 1 เวอร์ชันสมมติว่าเป็นต้นฉบับ สำเนาครึ่งหนึ่งถูกลบออกจากโฟลเดอร์หนึ่งและอีกครึ่งหนึ่งจากโฟลเดอร์อื่น (ลบสำเนา 100%) 100% ของฉันมีไว้เพื่อทำสำเนาส่วนเกินไม่ใช่จำนวนทั้งหมด
Ferroao

@ Ferroao ฉันเห็น ในกรณีนั้นดูเหมือนว่าเมื่อ bash ทำการขยายเส้นทางแบบเรียกซ้ำผ่าน**มันจะสั่งรายการในลักษณะที่ทั้งสองโฟลเดอร์ถูกอินเตอร์ลีฟแทนที่จะเป็นโฟลเดอร์ 1 ทั้งหมดจากนั้นทุกโฟลเดอร์ 2 สคริปต์จะปล่อย 'ดั้งเดิม' ตัวแรกเสมอ มันยอดนิยมในขณะที่มันวนซ้ำผ่านรายการ คุณสามารถนำecho $fileหน้าreadบรรทัดเพื่อดูว่านี่เป็นเรื่องจริงหรือไม่
SiegeX

45

fdupesเป็นเครื่องมือที่คุณเลือก หากต้องการค้นหาไฟล์ที่ซ้ำกันทั้งหมด (ตามเนื้อหาไม่ใช่ตามชื่อ) ในไดเรกทอรีปัจจุบัน:

fdupes -r .

หากต้องการยืนยันการลบไฟล์ที่ซ้ำกันด้วยตนเอง:

fdupes -r -d .

หากต้องการลบสำเนาทั้งหมดโดยอัตโนมัติ แต่ไฟล์แรกที่ทำซ้ำแต่ละไฟล์ ( ได้รับคำเตือนคำเตือนนี้เป็นการลบไฟล์ตามที่ร้องขอ ):

fdupes -r -f . | grep -v '^$' | xargs rm -v

ฉันขอแนะนำให้ตรวจสอบไฟล์ด้วยตนเองก่อนที่จะลบ:

fdupes -rf . | grep -v '^$' > files
... # check files
xargs -a files rm -v

ใช้งานได้ดี แต่ล้มเหลวหากชื่อไฟล์มีช่องว่าง
Daniel Wolf

1
@DanielWolf ลองกับตัวเลือก xargs-d '\n'
Jakob

1
นอกจากนี้ fdupes เวอร์ชันใหม่มีตัวเลือกในตัวเพื่อลบทั้งหมดยกเว้นไฟล์แรกในรายการไฟล์ที่ซ้ำกัน: fdupes -rdN .โดยที่ -r สามารถเรียกซ้ำได้ - d คือการลบและ -N ไม่พร้อมรับคำ
Rand

ขอบคุณนี่คือสิ่งที่โดดเด่นเพราะสามารถตรวจสอบซ้ำกันมากกว่า 2 และช่วยให้คุณเลือกหนึ่งในรายการที่คุณต้องการรักษา (หรือทั้งหมด)
Smeterlink


1

เป็นบิตขี้เกียจก็ไม่ได้พาฉันนานในการหาคนออนไลน์

อันดับแรกคุณต้องสร้างการตรวจสอบ CRC ของแต่ละไฟล์เนื่องจากคุณต้องการลบรายการที่ซ้ำกันออกไปอย่างชัดเจน

cksum  *.jpg | sort -n > filelist

จากนั้นทำซ้ำในรายการไฟล์นี้อ่านใน checksum และชื่อไฟล์ หากสอง checksums เหมือนกันไฟล์จะถูกลบ ใช้งานได้เนื่องจากการเรียงลำดับเป็นตัวเลขและเรียงลำดับเฉพาะบน checksums ซึ่งจัดกลุ่มไฟล์ที่ซ้ำกัน

old=""
while read sum lines filename
do
      if [[ "$sum" != "$old" ]] ; then
            old="$sum"
            continue
      fi
      rm -f "$filename"
done < filelist

เห็นได้ชัดว่ามันไม่ทำงานซ้ำ


1

จะทดสอบไฟล์ที่มีเนื้อหาไม่เหมือนใครได้อย่างไร

if diff "$file1" "$file2" > /dev/null; then
    ...

เราจะรับรายการไฟล์ในไดเรกทอรีได้อย่างไร?

files="$( find ${files_dir} -type f )"

เราสามารถรับไฟล์ 2 ไฟล์จากรายการนั้นและตรวจสอบว่าชื่อของพวกเขาแตกต่างกันและเนื้อหาเหมือนกัน

#!/bin/bash
# removeDuplicates.sh

files_dir=$1
if [[ -z "$files_dir" ]]; then
    echo "Error: files dir is undefined"
fi

files="$( find ${files_dir} -type f )"
for file1 in $files; do
    for file2 in $files; do
        # echo "checking $file1 and $file2"
        if [[ "$file1" != "$file2" && -e "$file1" && -e "$file2" ]]; then
            if diff "$file1" "$file2" > /dev/null; then
                echo "$file1 and $file2 are duplicates"
                rm -v "$file2"
            fi
        fi
    done
done

ตัวอย่างเช่นเรามี dir:

$> ls .tmp -1
all(2).txt
all.txt
file
text
text(2)

มีไฟล์ที่ไม่ซ้ำกัน 3 ไฟล์เท่านั้น

ให้เรียกใช้สคริปต์นั้น:

$> ./removeDuplicates.sh .tmp/
.tmp/text(2) and .tmp/text are duplicates
removed `.tmp/text'
.tmp/all.txt and .tmp/all(2).txt are duplicates
removed `.tmp/all(2).txt'

และเราได้ไฟล์เพียง 3 ไฟล์เท่านั้น

$> ls .tmp/ -1
all.txt
file
text(2)

1

ฉันเขียนสคริปต์ตัวจิ๋วนี้เพื่อลบไฟล์ที่ซ้ำกัน

https://gist.github.com/crodas/d16a16c2474602ad725b

โดยทั่วไปจะใช้ไฟล์ชั่วคราว ( /tmp/list.txt) เพื่อสร้างแผนที่ของไฟล์และแฮชของพวกเขา ต่อมาฉันใช้ไฟล์นั้นและความมหัศจรรย์ของท่อ Unix เพื่อทำสิ่งที่เหลือ

สคริปต์จะไม่ลบอะไรเลย แต่จะพิมพ์คำสั่งเพื่อลบไฟล์

mfilter.sh ./dir | bash

หวังว่ามันจะช่วย


1

รุ่นที่กระชับมากขึ้นในการลบไฟล์ที่ซ้ำกัน (เพียงหนึ่งบรรทัด)

young@ubuntu-16:~/test$ md5sum `find ./ -type f` | sort -k1 | uniq -w32 -d | xargs rm -fv

find_same_size.sh

#!/usr/bin/env bash
#set -x
#This is small script can find same size of files.
find_same_size(){

if [[ -z $1 || ! -d $1 ]]
then
echo "Usage $0 directory_name" ;
 exit $?
else
dir_name=$1;
echo "current directory is $1"



for i in $(find $dir_name -type f); do
   ls -fl $i
done | awk '{f=""
        if(NF>9)for(i=9;i<=NF;i++)f=f?f" "$i:$i; else f=$9;
        if(a[$5]){ a[$5]=a[$5]"\n"f; b[$5]++;} else a[$5]=f} END{for(x     in b)print a[x] }' | xargs stat -c "%s  %n" #For just list files
 fi
   }

find_same_size $1


young@ubuntu-16:~/test$ bash find_same_size.sh tttt/ | awk '{ if($1 !~   /^([[:alpha:]])+/) print $2}' | xargs md5sum | uniq -w32 -d | xargs rm -vf

0

ฉันพบวิธีที่ง่ายกว่าในการทำงานเดียวกัน

for i in `md5sum * | sort -k1 | uniq -w32 -d|awk '{print $2}'`; do
rm -rf $i
done

0

คำตอบที่เหลืออยู่ส่วนใหญ่และอาจเป็นไปได้ทั้งหมดนั้นไม่มีประสิทธิภาพอย่างมากโดยการคำนวณการตรวจสอบของแต่ละไฟล์ในไดเรกทอรีที่จะประมวลผล

คำสั่งที่มีขนาดใหญ่กว่าวิธีแรกที่อาจทำได้คือการหาขนาดของไฟล์แต่ละไฟล์ซึ่งเกือบจะทันที ( lsหรือstat) จากนั้นคำนวณและเปรียบเทียบ checksums สำหรับไฟล์ที่มีขนาดไม่ซ้ำกันเท่านั้น


0

นี่ไม่ใช่สิ่งที่คุณถาม แต่ฉันคิดว่าบางคนอาจพบว่ามีประโยชน์เมื่อเช็คซัมไม่เหมือนกัน แต่ชื่อนั้นคล้ายกัน (มีคำต่อท้ายอยู่ในวงเล็บ) สคริปต์นี้จะลบไฟล์ที่มีคำต่อท้ายเป็น ("หลัก")

#! /bin/bash
# Warning: globstar excludes hidden directories.
# Turn on recursive globbing (in this script) or exit if the option is not supported:
shopt -s globstar || exit
for f in **
do
extension="${f##*.}"
#get only files with parentheses suffix
FILEWITHPAR=$( echo "${f%.*}".$extension | grep -o -P "(.*\([0-9]\)\..*)")
# print file to be possibly deleted
if [ -z "$FILEWITHPAR" ] ;then
:
else
echo "$FILEWITHPAR ident"
# identify if a similar file without suffix exists
FILENOPAR=$(echo $FILEWITHPAR | sed -e 's/^\(.*\)([0-9])\(.*\).*/\1\2/')
echo "$FILENOPAR exists?"
if [ -f "$FILENOPAR" ]; then
#delete file with suffix in parentheses
echo ""$FILEWITHPAR" to be deleted"
rm -Rf "$FILEWITHPAR"
else
echo "no"
fi
fi
done

-3

ผมพบว่าโปรแกรมขนาดเล็กที่ช่วยลดความยุ่งยากจริงๆชนิดของงานนี้: fdupes


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