Xóa tất cả nhưng mỗi tệp thứ 12


14

Tôi có một vài ngàn tệp ở định dạng tên tệp.12345.end. Tôi chỉ muốn giữ mỗi tệp thứ 12, vì vậy tệp.00012.end, tệp.00024.end ... tệp.99996.end và xóa mọi thứ khác.

Các tệp cũng có thể có số sớm hơn trong tên tệp của chúng và thường có dạng: file.00064.name.99999.end

Tôi sử dụng Bash shell và không thể tìm ra cách lặp qua các tệp và sau đó lấy ra số và kiểm tra xem nó có number%%12=0 xóa tệp hay không. Ai giúp tôi với?

Cảm ơn bạn, Dorina


Là số lượng tập tin chỉ phụ thuộc vào tên tệp?
Arronical

Ngoài ra, các tệp luôn có 5 chữ số, và hậu tố và tiền tố luôn giống nhau phải không?
Arronical

Có nó luôn luôn là 5 chữ số. Tôi không chắc chắn nếu tôi nhận được câu hỏi đầu tiên của bạn đúng. Các tệp có tên tệp khác nhau là khác nhau và tôi cần các tệp cụ thể này có các số 00012,
00024

3
@Dorina vui lòng chỉnh sửa câu hỏi của bạn và làm cho nó rõ ràng. Nó thay đổi mọi thứ!
terdon

2
Và tất cả chúng đều nằm trong cùng một thư mục, phải không?
Sergiy Kolodyazhnyy

Câu trả lời:


18

Đây là một giải pháp Perl. Điều này sẽ nhanh hơn nhiều cho hàng ngàn tệp:

perl -e '@bad=grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV; unlink @bad' *

Mà có thể được cô đọng thêm vào:

perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *

Nếu bạn có quá nhiều tệp và không thể sử dụng đơn giản *, bạn có thể làm một số thứ như:

perl -e 'opendir($d,"."); unlink grep{/(\d+)\.end/ && $1 % 12 != 0} readdir($dir)'

Về tốc độ, đây là so sánh về phương pháp này và phương pháp vỏ được cung cấp trong một trong những câu trả lời khác:

$ touch file.{01..64}.name.{00001..01000}.end
$ ls | wc
  64000   64000 1472000
$ time for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done

real    2m44.258s
user    0m9.183s
sys     1m7.647s

$ touch file.{01..64}.name.{00001..01000}.end
$ time perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *

real    0m0.610s
user    0m0.317s
sys     0m0.290s

Như bạn có thể thấy, sự khác biệt là rất lớn, như mong đợi .

Giải trình

  • Điều -enày chỉ đơn giản là nói perlđể chạy tập lệnh được đưa ra trên dòng lệnh.
  • @ARGVlà một biến đặc biệt chứa tất cả các đối số được đưa ra cho tập lệnh. Vì chúng tôi đang cung cấp cho nó *, nó sẽ chứa tất cả các tệp (và thư mục) trong thư mục hiện tại.
  • Các grepsẽ tìm kiếm thông qua danh sách các tên tập tin và tìm kiếm bất kỳ phù hợp với một chuỗi các con số, dấu chấm và end( /(\d+)\.end/).

  • Bởi vì các số ( \d) nằm trong một nhóm bắt (dấu ngoặc đơn), chúng được lưu dưới dạng $1. Vì vậy, grepsau đó sẽ kiểm tra xem số đó có phải là bội số của 12 hay không và nếu không, tên tệp sẽ được trả về. Nói cách khác, mảng @badgiữ danh sách các tệp sẽ bị xóa.

  • Danh sách này sau đó được chuyển đến để unlink()loại bỏ các tệp (nhưng không phải thư mục).


12

Cho rằng tên tệp của bạn ở định dạng file.00064.name.99999.end, trước tiên chúng tôi cần cắt bỏ mọi thứ trừ số của chúng tôi. Chúng tôi sẽ sử dụng một forvòng lặp để làm điều này.

Chúng ta cũng cần nói với shell Bash sử dụng cơ sở 10, bởi vì số học Bash sẽ coi chúng là số bắt đầu bằng 0 là cơ sở 8, điều này sẽ gây rối cho chúng ta.

Là một tập lệnh, sẽ được khởi chạy khi trong thư mục chứa các tệp sử dụng:

#!/bin/bash

for f in ./*
do
  if [[ -f "$f" ]]; then
    file="${f%.*}"
    if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
      rm "$f"
    fi
  else
    echo "$f is not a file, skipping."
  fi
done

Hoặc bạn có thể sử dụng lệnh xấu xí rất dài này để làm điều tương tự:

for f in ./* ; do if [[ -f "$f" ]]; then file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; else echo "$f is not a file, skipping."; fi; done

Để giải thích tất cả các phần:

  • for f in ./* có nghĩa là cho tất cả mọi thứ trong thư mục hiện tại, do .... Điều này đặt từng tệp hoặc thư mục được tìm thấy dưới dạng biến $ f.
  • if [[ -f "$f" ]]kiểm tra xem mục được tìm thấy có phải là một tệp không, nếu không chúng ta bỏ qua echo "$f is not...phần đó, điều đó có nghĩa là chúng ta không bắt đầu xóa các thư mục một cách tình cờ.
  • file="${f%.*}"đặt biến $ file làm tên tệp cắt xén bất cứ thứ gì đến sau cái cuối cùng ..
  • if [[ $((10#${file##*.} % 12)) -eq 0 ]]là nơi Số học chính bắt đầu. ${file##*.}Trims mọi thứ trước tên cuối cùng .trong tên tệp của chúng tôi mà không cần gia hạn. $(( $num % $num2 ))là cú pháp để số học Bash sử dụng phép toán modulo, 10#lúc bắt đầu bảo Bash sử dụng cơ sở 10, để đối phó với các số 0 dẫn đầu phiền phức đó. $((10#${file##*.} % 12))sau đó để lại cho chúng tôi phần còn lại của số tên tệp chia cho 12. -ne 0kiểm tra xem phần còn lại có "không bằng" không.
  • Nếu phần còn lại không bằng 0, tệp sẽ bị xóa bằng rmlệnh, bạn có thể muốn thay thế rmbằng echokhi chạy lần đầu tiên này, để kiểm tra xem bạn có xóa các tệp dự kiến ​​không.

Giải pháp này không đệ quy, nghĩa là nó sẽ chỉ xử lý các tệp trong thư mục hiện tại, nó sẽ không đi vào bất kỳ thư mục con nào.

Các iftuyên bố với các echolệnh để cảnh báo về thư mục là không thực sự cần thiết như rmtrên riêng của nó sẽ phàn nàn về thư mục, và không xóa chúng, vì vậy:

#!/bin/bash

for f in ./*
do
  file="${f%.*}"
  if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
    rm "$f"
  fi
done

Hoặc là

for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done

Sẽ hoạt động chính xác quá.


5
Gọi rmvài ngàn lần có thể khá chậm. Tôi đề nghị echothay vào tên tệp và chuyển đầu ra của vòng lặp sang xargs rm(thêm tùy chọn nếu cần) : for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --.
David Foerster

Tôi đã chỉnh sửa để bao gồm cải thiện tốc độ được đề xuất của bạn.
Arronical

Thực tế sau khi thử nghiệm trên một thư mục với 55999 tệp, phiên bản gốc mất 2 phút 48 giây, xargsphiên bản mất 5 phút 1 giây. Điều này có thể là do chi phí trên echo@DavidFoerster?
Arronical

Lạ Đối với 60.000 tệp, tôi nhận được 0m0.659s / 0m0.545s / 0m0.380s (thực / người dùng / sys) với time { for f in *; do echo "$f"; done | xargs rm; }so với 1m11.450s / 0m10.695s / 0m16.800s time { for f in *; do rm "$f"; done; }trên một tmpfs. Bash là v4.3.11, Kernel là v4.4.19.
David Foerster

6

Bạn có thể sử dụng mở rộng khung Bash để tạo tên chứa mỗi số thứ 12. Hãy tạo một số dữ liệu thử nghiệm

$ touch file.{0..9}{0..9}{0..9}{0..9}{0..9}.end # create test data
$ mv file.00024.end file.00024.end.name.99999.end # testing this form of filenames

Sau đó chúng ta có thể sử dụng như sau

$ ls 'file.'{00012..100..12}* # print these with numbers less than 100
file.00012.end                 file.00036.end  file.00060.end  file.00084.end
file.00024.end.name.99999.end  file.00048.end  file.00072.end  file.00096.end
$ rm 'file.'{00012..100000..12}* # do the job

Tuy nhiên, hoạt động rất chậm đối với số lượng lớn tệp - cần có thời gian và bộ nhớ để tạo ra hàng ngàn tên - vì vậy đây là một mẹo để giải pháp hiệu quả thực sự.


Tôi thích môn đánh gôn trên cái này.
David Foerster

1

Một chút dài, nhưng là những gì đến với tâm trí của tôi.

 for num in $(seq 1 1 11) ; do
     for sequence in $(seq -f %05g $num 12 99999) ; do
         rm file.$sequence.end.99999;
     done
 done

Giải thích: Xóa mỗi tập tin thứ 12 mười một lần.


0

Trong tất cả sự khiêm tốn tôi nghĩ rằng giải pháp này đẹp hơn rất nhiều so với câu trả lời khác:

find . -name '*.end' -depth 1 | awk 'NR%12 != 0 {print}' | xargs -n100 rm

Một lời giải thích nhỏ: Đầu tiên chúng tôi tạo ra một danh sách các tập tin với find. Chúng tôi nhận được tất cả các tệp có tên kết thúc .endvà ở độ sâu 1 (nghĩa là chúng trực tiếp trong thư mục làm việc chứ không phải trong bất kỳ thư mục con nào. Bạn có thể bỏ qua nếu không có thư mục con). Danh sách đầu ra sẽ được sắp xếp theo thứ tự abc.

Sau đó, chúng tôi đưa danh sách đó vào awk, nơi chúng tôi sử dụng biến đặc biệt NRlà số dòng. Chúng tôi loại bỏ mọi tập tin thứ 12 bằng cách in các tập tin ở đâu NR%12 != 0. Các awklệnh có thể được rút ngắn xuống còn awk 'NR%12', bởi vì kết quả của các nhà điều hành modulo được hiểu như là một giá trị boolean và {print}được mặc nhiên thực hiện anyway.

Vì vậy, bây giờ chúng tôi có một danh sách các tập tin cần phải xóa, chúng tôi có thể làm với xargs và rm. xargschạy lệnh đã cho ( rm) với đầu vào tiêu chuẩn làm đối số.

Nếu bạn có nhiều tệp, bạn sẽ gặp lỗi khi nói "danh sách đối số quá dài" (trên máy của tôi giới hạn là 256 kB và mức tối thiểu mà POSIX yêu cầu là 4096 byte). Điều này có thể tránh được bằng -n 100cờ, nó sẽ phân tách các đối số sau mỗi 100 từ (không phải dòng, một điều cần chú ý nếu tên tệp của bạn có khoảng trắng) và thực thi một rmlệnh riêng biệt , mỗi lệnh chỉ có 100 đối số.


3
Có một vài vấn đề với cách tiếp cận của bạn: -depthcần phải có trước -name; ii) điều này sẽ thất bại nếu bất kỳ tên tệp nào chứa khoảng trắng; iii) bạn cho rằng các tệp sẽ được liệt kê theo thứ tự số tăng dần (đó là những gì bạn awkđang kiểm tra) nhưng điều này gần như chắc chắn sẽ không xảy ra. Do đó, điều này sẽ xóa một tập hợp các tệp ngẫu nhiên.
terdon

ôi! Bạn hoàn toàn đúng, xấu của tôi (bình luận chỉnh sửa). Tôi đã nhận được lỗi vì vị trí sai và không nhớ -depth. Tuy nhiên, đó là vấn đề ít nhất ở đây, vấn đề quan trọng nhất là bạn đang xóa một tập tin ngẫu nhiên chứ không phải những tập tin mà OP muốn.
terdon

Ồ, và không, -depthkhông có giá trị và nó trái ngược với những gì bạn nghĩ. Xem man find: "-depth Xử lý nội dung của mỗi thư mục trước chính thư mục đó." Vì vậy, điều này thực sự sẽ đi xuống các thư mục con và tàn phá khắp nơi.
terdon

I) Cả hai -depth n-maxdepth ntồn tại. Cái trước đòi hỏi độ sâu phải chính xác n và với cái sau có thể là <= n. II). Vâng, đó là xấu nhưng đối với ví dụ cụ thể này, nó không phải là mối quan tâm. Bạn có thể sửa nó bằng cách sử dụng find ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm, sử dụng byte null làm dấu tách bản ghi (không được phép trong tên tệp). III) Một lần nữa, trong trường hợp này, giả định là hợp lý. Nếu không, bạn có thể chèn sort -ngiữa findawk, hoặc chuyển hướng findđến một tệp và sắp xếp nó theo cách bạn muốn.
dùng593851

3
Ah, có lẽ bạn đang sử dụng OSX. Đó là một cách thực hiện rất khác find. Tuy nhiên, một lần nữa, vấn đề chính là bạn cho rằng findsẽ trả về một danh sách được sắp xếp. Nó không.
terdon

0

Để chỉ sử dụng bash, cách tiếp cận đầu tiên của tôi sẽ là: 1. di chuyển tất cả các tệp bạn muốn giữ vào một thư mục khác (ví dụ: tất cả những người có số trong tên tệp là bội số của 12) sau đó 2. xóa tất cả các tệp còn lại trong thư mục, sau đó 3. đặt các tệp nhiều trong số 12 tệp bạn giữ lại vị trí của chúng. Vì vậy, một cái gì đó như thế này có thể làm việc:

cd dir_containing_files
mkdir keep_these_files
n=0
while [ "${n}" -lt 99999 ]; do
  padded_n="`echo -n "00000${n}" | tail -c 5`"
  mv "filename${padded_n}.end" keep_these_files/
  n=$[n+12]
done
rm filename*.end
mv keep_these_files/* .
rmdir keep_these_files

Tôi thích cách tiếp cận, nhưng làm thế nào để bạn tạo ra filenamephần đó nếu nó không nhất quán?
Arronical
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.