tìm và loại bỏ trùng lặp trong một thư mục


12

Tôi có một thư mục chứa nhiều tệp img và một số trong số chúng giống hệt nhau nhưng chúng đều có tên khác nhau. Tôi cần loại bỏ trùng lặp nhưng không có công cụ bên ngoài chỉ với một bashtập lệnh. Tôi là người mới bắt đầu sử dụng Linux. Tôi đã thử lồng vòng lặp để so sánh các md5khoản tiền và tùy thuộc vào kết quả loại bỏ nhưng có gì đó không đúng với cú pháp và nó không hoạt động. Có ai giúp đỡ không?

những gì tôi đã thử là ...

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

Tôi có: test: too many arguments


Vui lòng bao gồm bất kỳ thông báo lỗi bạn nhận được trong câu hỏi của bạn.
terdon

Tại sao bạn không thể sử dụng các công cụ bên ngoài như fdupes? Câu trả lời của @terdon thật tuyệt vời, nhưng nó thực sự làm nổi bật lý do tại sao sử dụng một công cụ tốt là cách tốt nhất có thể. Nếu đó là một loại phần cứng hoặc máy chủ chuyên dụng, bạn vẫn có thể truy cập nó qua mạng, v.v. từ một máy có sẵn các công cụ như fdupes.
Joe

Câu trả lời:


28

Có khá nhiều vấn đề trong kịch bản của bạn.

  • Đầu tiên, để gán kết quả của một lệnh cho một biến, bạn cần đặt nó trong backtics ( `command`) hoặc, tốt nhất là , $(command). Bạn có nó trong dấu ngoặc đơn ( 'command') thay vì gán kết quả của lệnh cho biến của bạn, hãy gán chính lệnh đó dưới dạng chuỗi. Do đó, bạn testthực sự là:

    $ echo "test $sum1=$sum2"
    test find $i -type f -iname "*.jpg" -exec md5sum {} \;=find $j -type f -iname "*.jpg" -exec md5sum {} \;
  • Vấn đề tiếp theo là lệnh md5sumtrả về nhiều hơn chỉ là hàm băm:

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

    Bạn chỉ muốn so sánh trường đầu tiên, vì vậy bạn nên phân tích md5sumđầu ra bằng cách chuyển nó qua một lệnh chỉ in trường đầu tiên:

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

    hoặc là

    find $i -type f -iname "*.png" -exec md5sum '{}' \; | awk '{print $1}' 
  • Ngoài ra, findlệnh sẽ trả về nhiều trận đấu, không chỉ một và mỗi trận đấu đó sẽ được nhân đôi bởi lần thứ hai find. Điều này có nghĩa là tại một số điểm bạn sẽ so sánh cùng một tệp với chính nó, md5sum sẽ giống hệt nhau và cuối cùng bạn sẽ xóa tất cả các tệp của mình (Tôi đã chạy tệp này trên một thư mục thử nghiệm có chứa a.jpgb.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
  • Bạn không muốn chạy for i in directory_pathtrừ khi bạn vượt qua một loạt các thư mục. Nếu tất cả các tệp này nằm trong cùng một thư mục, bạn muốn chạy for i in $(find directory_path -iname "*.jpg") để đi qua tất cả các tệp.

  • Đó là một ý tưởng tồi để sử dụng forcác vòng lặp với đầu ra của find. Bạn nên sử dụng whilecác vòng lặp hoặc Globing :

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

    hoặc, nếu tất cả các tệp của bạn nằm trong cùng một thư mục:

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

    Tùy thuộc vào trình bao của bạn và các tùy chọn bạn đã đặt, bạn có thể sử dụng tính năng toàn cầu ngay cả đối với các tệp trong thư mục con nhưng chúng ta không đi vào đó.

  • Cuối cùng, bạn cũng nên trích dẫn các biến đường dẫn thư mục khác với khoảng trắng sẽ phá vỡ tập lệnh của bạn.

Tên tệp có thể chứa dấu cách, dòng mới, dấu gạch chéo ngược và các ký tự lạ khác, để xử lý chúng chính xác trong whilevòng lặp, bạn sẽ cần thêm một số tùy chọn khác. Những gì bạn muốn viết là một cái gì đó như:

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

Một cách thậm chí đơn giản hơn sẽ là:

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

Một phiên bản tốt hơn có thể xử lý khoảng trắng trong tên tệp:

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

Kịch bản Perl nhỏ này sẽ chạy qua các kết quả của findlệnh (tức là md5sum và tên tệp). Các -alựa chọn cho perldòng chia đầu vào ở khoảng trắng và lưu chúng trong Fmảng, vì vậy $F[0]sẽ là md5sum và $F[1]tên tập tin. Md5sum được lưu trong hàm băm kvà tập lệnh sẽ kiểm tra xem hàm băm đã được nhìn thấy chưa ( if $k{$F[0]}>1) và xóa tệp nếu nó có ( system("rm $F[1]")).


Mặc dù điều đó sẽ hoạt động, nhưng nó sẽ rất chậm đối với các bộ sưu tập hình ảnh lớn và bạn không thể chọn tập tin nào để giữ. Có nhiều chương trình xử lý việc này theo cách thanh lịch hơn bao gồm:


+1 cho đoạn trích Perl. Thực sự thanh lịch! Bạn cũng có thể sử dụng riêng của Perl unlinkthay vì thực hiện systemcuộc gọi.
Joseph R.

@JosephR. cảm ơn :). Mặc dù có một lỗi, nhưng nó sẽ thất bại đối với các tên tệp có khoảng trắng do chỉ có các ký tự đầu tiên của tên cho đến khoảng trắng đầu tiên sẽ xuất hiện $F[1]. Đã sửa nó bằng các lát mảng. Đối với unlink () tôi biết, nhưng muốn giữ mức độ sai lệch ở mức tối thiểu và cuộc gọi hệ thống sẽ dễ hiểu hơn nếu bạn không biết Perl.
terdon

13

Có một chương trình tiện lợi được gọi là fdupesđơn giản hóa toàn bộ quá trình và nhắc nhở người dùng xóa các bản sao. Tôi nghĩ rằng nó là giá trị kiểm tra:

$ 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

Về cơ bản, nó nhắc tôi giữ tập tin nào , tôi đã gõ 1 , và nó xóa cái thứ hai.

Các tùy chọn thú vị khác là:

-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

Từ ví dụ của bạn, có lẽ bạn muốn chạy nó dưới dạng:

fdupes --recurse --delete --noprompt DIRECTORY_WITH_DUPLICATES

Xem man fdupescho tất cả các tùy chọn có sẵn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.