ค้นหาและลบรายการที่ซ้ำในไดเรกทอรี


12

ฉันมีไดเรกทอรีที่มีไฟล์ img หลายไฟล์และบางไฟล์เหมือนกัน แต่มีชื่อแตกต่างกัน ฉันต้องการลบรายการที่ซ้ำกัน แต่ไม่มีเครื่องมือภายนอกที่มีbashสคริปต์เท่านั้น ฉันเป็นผู้เริ่มต้นใน Linux ฉันลองซ้อนสำหรับ loop เพื่อเปรียบเทียบmd5ผลรวมและขึ้นอยู่กับผลการลบ แต่มีบางอย่างผิดปกติกับไวยากรณ์และมันไม่ทำงาน ความช่วยเหลือใด ๆ

สิ่งที่ฉันพยายามคือ ...

for i in directory_path; do
    sum1='find $i -type f -iname "*.jpg" -exec md5sum '{}' \;'
    for j in directory_path; do
        sum2='find $j -type f -iname "*.jpg" -exec md5sum '{}' \;'
        if test $sum1=$sum2 ; then rm $j ; fi
    done
done

ฉันเข้าใจ: test: too many arguments


โปรดระบุข้อความผิดพลาดที่คุณได้รับในคำถามของคุณ
terdon

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

คำตอบ:


28

สคริปต์ของคุณมีปัญหาเล็กน้อย

  • ครั้งแรกในเพื่อที่จะกำหนดผลของคำสั่งไปยังตัวแปรที่คุณต้องใส่มันทั้งใน backtics ( `command`) $(command)หรือโดยเฉพาะอย่างยิ่ง คุณมีในเครื่องหมายคำพูดเดี่ยว ( 'command') ซึ่งแทนที่จะกำหนดผลลัพธ์ของคำสั่งของคุณให้กับตัวแปรของคุณกำหนดคำสั่งของตัวเองเป็นสตริง ดังนั้นtestจริงๆแล้วคุณคือ:

    $ echo "test $sum1=$sum2"
    test find $i -type f -iname "*.jpg" -exec md5sum {} \;=find $j -type f -iname "*.jpg" -exec md5sum {} \;
  • ปัญหาต่อไปคือคำสั่งmd5sumส่งคืนมากกว่าแฮช:

    $ md5sum /etc/fstab
    46f065563c9e88143fa6fb4d3e42a252  /etc/fstab

    คุณต้องการเปรียบเทียบฟิลด์แรกเท่านั้นดังนั้นคุณควรแยกmd5sumเอาต์พุตโดยส่งผ่านคำสั่งที่พิมพ์เฉพาะฟิลด์แรก:

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | cut -f 1 -d ' '

    หรือ

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}' 
  • นอกจากนี้findคำสั่งจะกลับมาตรงกับที่หลาย ๆ findคนไม่ได้เป็นเพียงหนึ่งและแต่ละการแข่งขันเหล่านั้นจะถูกทำซ้ำโดยที่สอง ซึ่งหมายความว่าในบางจุดคุณจะเปรียบเทียบไฟล์เดียวกันกับตัวเอง md5sum จะเหมือนกันและคุณจะลบไฟล์ทั้งหมดของคุณ (ฉันรันไฟล์นี้ในไฟล์ทดสอบที่มีa.jpgและb.jpg):

    for i in $(find . -iname "*.jpg"); do
      for j in $(find . -iname "*.jpg"); do
         echo "i is: $i and j is: $j"
      done
    done   
    i is: ./a.jpg and j is: ./a.jpg   ## BAD, will delete a.jpg
    i is: ./a.jpg and j is: ./b.jpg
    i is: ./b.jpg and j is: ./a.jpg
    i is: ./b.jpg and j is: ./b.jpg   ## BAD will delete b.jpg
  • คุณไม่ต้องการเรียกใช้for i in directory_pathจนกว่าคุณจะผ่านไดเรกทอรีต่างๆ หากไฟล์เหล่านี้อยู่ในไดเรกทอรีเดียวกันคุณต้องการเรียกใช้for i in $(find directory_path -iname "*.jpg") เพื่อดูไฟล์ทั้งหมด

  • มันเป็นความคิดที่ดีที่จะใช้forลูปกับผลลัพธ์ของการค้นหา คุณควรใช้whileลูปหรือกลม :

    find . -iname "*.jpg" | while read i; do [...] ; done

    หรือหากไฟล์ทั้งหมดของคุณอยู่ในไดเรกทอรีเดียวกัน:

    for i in *jpg; do [...]; done

    คุณสามารถใช้ globbing แม้กระทั่งไฟล์ในไดเรกทอรีย่อยทั้งนี้ขึ้นอยู่กับเชลล์และตัวเลือกที่คุณตั้งไว้ แต่อย่าเข้าไปดูที่นี่

  • ท้ายสุดคุณควรอ้างอิงตัวแปรของคุณเส้นทางอื่นที่มีช่องว่างจะทำให้สคริปต์ของคุณแตก

ชื่อไฟล์สามารถมีช่องว่างบรรทัดใหม่แบ็กสแลชและอักขระแปลก ๆ เพื่อจัดการกับไฟล์เหล่านั้นอย่างถูกต้องในwhileลูปคุณจะต้องเพิ่มตัวเลือกเพิ่มเติม สิ่งที่คุณต้องการเขียนคือ:

find dir_path -type f -iname "*.jpg" -print0 | while IFS= read -r -d '' i; do
  find dir_path -type f -iname "*.jpg" -print0 | while IFS= read -r -d '' j; do
    if [ "$i" != "$j" ]
    then
      sum1=$(md5sum "$i" | cut -f 1 -d ' ' )
      sum2=$(md5sum "$j" | cut -f 1 -d ' ' )
      [ "$sum1" = "$sum2" ] && rm "$j"
    fi
  done
done

วิธีที่ง่ายกว่าคือ:

find directory_path -name "*.jpg" -exec md5sum '{}' + | 
 perl -ane '$k{$F[0]}++; system("rm $F[1]") if $k{$F[0]}>1'

รุ่นที่ดีกว่าที่สามารถจัดการกับช่องว่างในชื่อไฟล์:

find directory_path -name "*.jpg" -exec md5sum '{}' + | 
 perl -ane '$k{$F[0]}++; system("rm \"@F[1 .. $#F]\"") if $k{$F[0]}>1'

สคริปต์ Perl นี้เล็กน้อยจะทำงานผ่านผลลัพธ์ของfindคำสั่ง (เช่น md5sum และชื่อไฟล์) -aเลือกสำหรับperlสายแยกการป้อนข้อมูลในช่องว่างและบันทึกไว้ในFอาร์เรย์ดังนั้น$F[0]จะเป็น md5sum และ$F[1]ชื่อไฟล์ md5sum จะถูกบันทึกในแฮชkและสคริปต์จะตรวจสอบว่าแฮชถูกเห็นแล้ว ( if $k{$F[0]}>1) และลบไฟล์หากมี ( system("rm $F[1]"))


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

  • fdupes
  • fslint
  • ตัวเลือกอื่น ๆ ที่ระบุไว้ที่นี่

+1 สำหรับตัวอย่างของ Perl สง่างามจริงๆ! คุณสามารถใช้ Perl ของตัวเองunlinkแทนการsystemโทรออก
โจเซฟอาร์

@JosephR ขอบคุณ :) มีข้อผิดพลาด $F[1]แต่ก็จะล้มเหลวสำหรับชื่อไฟล์ที่มีพื้นที่ตั้งแต่เพียงตัวอักษรแรกของชื่อขึ้นไปยังพื้นที่แรกที่จะอยู่ใน แก้ไขโดยใช้ชิ้นส่วนของอาร์เรย์ สำหรับ unlink () ฉันรู้ แต่ต้องการทำให้ perlisms มีค่าน้อยที่สุดและการเรียกของระบบนั้นง่ายต่อการเข้าใจถ้าคุณไม่รู้จัก Perl
terdon

13

มีโปรแกรมที่ดีที่เรียกfdupesว่าช่วยลดความยุ่งยากของกระบวนการทั้งหมดและแจ้งให้ผู้ใช้ลบรายการที่ซ้ำกัน ฉันคิดว่ามันคุ้มค่าที่จะตรวจสอบ:

$ fdupes --delete DIRECTORY_WITH_DUPLICATES
[1] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz        
[2] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz.1

Set 1 of 1, preserve files [1 - 2, all]: 1

   [+] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz
   [-] DIRECTORY_WITH_DUPLICATES/package-0.1-linux.tar.gz.1

โดยพื้นฐานแล้วจะแจ้งให้ฉันทราบว่าจะเก็บไฟล์ใดฉันพิมพ์1และลบไฟล์ที่สอง

ตัวเลือกที่น่าสนใจอื่น ๆ ได้แก่ :

-r --recurse
    for every directory given follow subdirectories encountered within

-N --noprompt
    when used together with --delete, preserve the first file in each set of duplicates and delete the others without prompting the user

จากตัวอย่างของคุณคุณอาจต้องการเรียกใช้เป็น:

fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES

ดูman fdupesตัวเลือกทั้งหมดที่มี

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