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 test
thự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 md5sum
trả 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, find
lệ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.jpg
và 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
Bạn không muốn chạy for i in directory_path
trừ 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 for
các vòng lặp với đầu ra của find. Bạn nên sử dụng while
cá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 while
vò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 find
lệnh (tức là md5sum và tên tệp). Các -a
lựa chọn cho perl
dòng chia đầu vào ở khoảng trắng và lưu chúng trong F
mả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 k
và 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: