Tìm và xóa các tập tin trùng lặp

Các tập tin trùng lặp là các bản sao của cùng 1 tập tin. Trong vài trường hợp, chúng ta có thể cần phải loại bỏ các tập tin trùng lặp và chỉ giữ lại 1 bản sao của chúng. Việc nhận dạng các tập tin trùng lặp bằng việc xem nội dung của chúng là 1 công việc thú vị. Chúng ta có thể thực hiện việc này bằng việc kết hợp các tiện ích trong Bash shell. Trong bài viết này, chúng ta sẽ tìm hiểu cách tìm kiếm các tập tin trùng lặp và thực hiện nhiều tác vụ dựa trên kết quả thu được.

Chúng ta có thể nhận dạng các tập tin trùng lặp bằng việc so sánh nội dung của tập tin. Sử dụng checksum là việc lý tưởng trong trường hợp này, các tập tin có cùng nội dung sẽ cho ra kết quả checksum giống nhau. Chúng ta có thể sử dụng dữ kiện này để xóa các tập tin trùng lặp.

Đầu tiên, chúng ta sẽ tạo vài tập tin văn bản như sau:

$ echo "hello" > test ; cp test test_copy1 ; cp test test_copy2;
$ echo "next" > other;

Viết kịch bản để xóa các tập tin trùng lặp trong Bash shell có nội dung như sau:

#!/bin/bash
#Filename: remove_duplicates.sh
#Description: Find and remove duplicate files and keep one sample 
# of each file.
ls -lS --time-style=long-iso | awk 'BEGIN { 
    getline; getline; 
    name1=$8; size=$5 
} 
{
    name2=$8; 
    if (size==$5) 
    { 
        "md5sum "name1 | getline; csum1=$1;
        "md5sum "name2 | getline; csum2=$1;
        if ( csum1==csum2 ) 
        {
            print name1; print name2
        }
    };
        size=$5; name1=name2; 
    }' | sort -u > duplicate_files 

    cat duplicate_files | xargs -I {} md5sum {} | sort | uniq -w 32 | awk '{ print "^"$2"$" }' | sort -u > duplicate_sample
    echo Removing..

    comm duplicate_files duplicate_sample -2 -3 | tee /dev/stderr | xargs rm
    echo Removed duplicates files successfully.

Chạy kịch bản trên:

$ ./remove_duplicates.sh

Kịch bản trên sẽ tìm các bản sao của cùng 1 tập tin trong 1 thư mục và xóa tất cả ngoại trừ 1 bản sao của tập tin đó. Chúng ta cùng nhìn sâu hơn vào nội dung kịch bản:

  • ls -lS => liệt kê chi tiết của các tập tin được sắp xếp theo kích thước tập tin trong thư mục hiện tại
  • –time-style=long-iso => in ra ngày tháng theo định dạng ISO.
  • awk sẽ đọc kết quả xuất ra của ls -lS và thực hiện các so sánh trên các cột và dòng của dữ liệu đầu vào để tìm ra các tập tin trùng lặp.

Logic của kịch bản của chúng ta sẽ như sau:

  • Chúng ta liệt kê các tập tin được sắp xếp theo kích thước để nhóm các tập tin có cùng kích thước với nhau. Các tập tin có cùng kích thước được nhận dạng là bước đầu tiên để tìm các tập tin giống nhau. Tiếp theo, chúng ta tính toán checksum của các tập tin. Nếu các checksum trùng khớp, các tập tin này là trùng lặp và các tập tin trùng lặp sẽ được xóa.
  • Khối BEGIN{} của lệnh awk được thực thi lần đầu trước khi các dòng được đọc từ tập tin. Việc đọc các dòng diễn ra trong khối {} và sau khi  kết thúc việc đọc và xử lý tất cả các dòng, khối END{} (bắt đầu từ lệnh cat đến hết kịch bản) được thực thi. Kết quả xuất ra của lệnh ls -lS là:
total 16
-rw-r--r-- 1 slynux slynux 5 2010-06-29 11:50 other
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test_copy1
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test_copy2
  • Dòng đầu tiên cho chúng ta biết tổng số tập tin. Chúng ta sử dụng getline để đọc dòng đầu tiên và bỏ qua nó. Chúng ta cần so sánh kích thước của từng dòng với dòng tiếp theo. Để làm việc đó, chúng ta đọc dòng thứ 1 sử dụng getline và lưu trữ tên và kích thước (nằm ở cột thứ 5 và thứ 8). Bây giờ, khi awk truy cập vào khối {} (nơi mà toàn bộ các dòng được đọc), khối này được thực thi cho mỗi lần đọc 1 dòng. Nó so sánh kích thước có được từ dòng hiện tại và kích thước của dòng trước được lưu trong biến size. Nếu chúng bằng nhau, có nghĩa là 2 tập tin là trùng lặp ở kích thước. Tiếp theo, chúng sẽ được kiểm tra bằng md5sum.
  • Kết quả xuất ra của lệnh bên ngoài có thể được đọc bên trong awk như sau:
"cmd"| getline
  • Sau đó, chúng ta nhận được kết quả xuất ra của 1 trong dòng trong biến $0 và kết quả xuất ra của từng cột có thể nhận được trong các biến $1, $2, …, $n. Ở đây, chúng ta đọc checksum md5 của các tập tin trong 2 biến csum1csum2.  Các biến name1name2 được dùng để lưu trữ tên của các tập tin. Nếu checksum của 2 tập tin giống nhau, chúng sẽ được xem là trùng lặp và được in ra. Chúng ta cần tìm 1 tập tin từ mỗi nhóm trùng lặp để chúng ta có thể xóa các tập tin trùng lặp kia. Chúng ta tính toán giá trị md5sum của các tập tin trùng lặp và in 1 tập tin từ mỗi nhóm trùng lặp bằng việc tìm các dòng riêng biệt, so sánh kết quả md5sum chứa các chuỗi băm theo sau bởi tên tập tin. 1 mẫu từ mỗi nhóm trùng lặp được ghi trong tập tin duplicate_sample.
  • Giờ chúng ta cần xóa tất cả các tập tin được liệt kê trong tập tin duplicate_files, ngoại trừ các tập tin được liệt kê trong tập tin duplicate_sample. Lệnh comm in ra các tập tin trong tập tin duplicate_files nhưng không có trong tập tin duplicate_sample.
  • Để thực hiện việc này, chúng ta sử dụng phép toán hiệu. comm luôn luôn chấp nhận các tập tin được sắp xếp. Do đó sort -u được dùng như bộ lọc trước khi chuyển hướng đến duplicate_filesduplicate_sample.
  • Lệnh tee ở đây được dùng để thực hiện việc truyền các tên tập tin đến lệnh rm. Lệnh tee viết các dòng xuất hiện trong stdin đến 1 tập tin và gửi chúng đến stdout. Chúng ta cũng có thể in văn bản đến terminal bằng việc chuyển hướng đến stderr. /dev/stderr là thiết bị tương ứng với stderr (standard error). Bằng việc chuyển hướng đến tập tin thiết bị stderr, văn bản xuất hiện qua stdin sẽ được in trong terminal như các lỗi bình thường.

Chúng ta hoàn thành xong việc tìm và xóa các tập tin trùng lặp trong Bash shell.

Chúc các bạn thành công!