ลบทั้งหมดยกเว้นไฟล์ที่ 12 ทุกไฟล์


14

ฉันมีไฟล์สองพันไฟล์ในรูปแบบชื่อไฟล์ 12345.end ฉันต้องการเก็บทุก ๆ ไฟล์ที่ 12 เท่านั้นดังนั้น file.00012.end, file.00024.end ... file.99996.end และลบทุกอย่างอื่น

ไฟล์อาจมีตัวเลขก่อนหน้านี้ในชื่อไฟล์และโดยปกติแล้วจะอยู่ในรูปแบบ: file.00064.name.99999.end

ฉันใช้ Bash shell และไม่สามารถหาวิธีวนลูปไฟล์จากนั้นรับตัวเลขและตรวจสอบว่ามันnumber%%12=0 ลบไฟล์หรือไม่ มีใครช่วยฉันบ้าง

ขอบคุณ Dorina


จำนวนไฟล์ขึ้นอยู่กับชื่อไฟล์หรือไม่?
59

นอกจากนี้ไฟล์จะมี 5 หลักเสมอและส่วนต่อท้ายและส่วนนำหน้าจะเหมือนกันหรือไม่
Arronical

ใช่มันคือ 5 หลักเสมอ ฉันไม่แน่ใจว่าคำถามแรกของคุณถูกไหม ไฟล์ที่มีชื่อไฟล์ต่างกันจะแตกต่างกันและฉันต้องการไฟล์เฉพาะเหล่านี้ซึ่งมีหมายเลข 00012, 00024 และอื่น ๆ อีกมากมาย
Dorina

3
@Dorina โปรดแก้ไขคำถามของคุณและทำให้ชัดเจน มันเปลี่ยนแปลงทุกอย่าง!
terdon

2
และพวกมันทั้งหมดอยู่ในไดเรกทอรีเดียวกันใช่ไหม
Sergiy Kolodyazhnyy

คำตอบ:


18

นี่คือทางออกของ Perl ควรเร็วกว่านี้สำหรับไฟล์หลายพันไฟล์:

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

ซึ่งสามารถย่อเพิ่มเติมลงใน:

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

หากคุณมีไฟล์มากเกินไปและไม่สามารถใช้งานง่าย*คุณสามารถทำสิ่งต่อไปนี้:

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

สำหรับความเร็วนี่เป็นการเปรียบเทียบวิธีนี้กับเชลล์ที่ให้ไว้ในคำตอบอย่างใดอย่างหนึ่ง:

$ 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

อย่างที่คุณเห็นความแตกต่างเป็นอย่างมากอย่างที่คาดไว้

คำอธิบาย

  • -eเป็นเพียงการบอกperlให้เรียกใช้สคริปต์ที่กำหนดในบรรทัดคำสั่ง
  • @ARGVเป็นตัวแปรพิเศษที่มีอาร์กิวเมนต์ทั้งหมดที่กำหนดให้กับสคริปต์ เนื่องจากเราจะให้มัน*มันจะมีไฟล์ทั้งหมด (และไดเรกทอรี) ในไดเรกทอรีปัจจุบัน
  • grepจะค้นหาผ่านรายการของชื่อไฟล์และรูปลักษณ์ใด ๆ ที่ตรงกับสตริงของตัวเลขที่จุดและ(end/(\d+)\.end/)

  • เพราะตัวเลข ( \d) อยู่ในกลุ่มการบันทึก (วงเล็บ) $1พวกเขาจะถูกบันทึกเป็น ดังนั้นความgrepประสงค์จะตรวจสอบว่าหมายเลขนั้นเป็นผลคูณของ 12 หรือไม่ถ้าไม่ใช่ชื่อไฟล์จะถูกส่งกลับ กล่าวอีกนัยหนึ่งอาร์เรย์@badเก็บรายการไฟล์ที่จะลบ

  • รายการจะถูกส่งผ่านไปยังunlink()ซึ่งจะลบไฟล์ (แต่ไม่ใช่ไดเรกทอรี)


12

เนื่องจากชื่อไฟล์ของคุณอยู่ในรูปแบบfile.00064.name.99999.endก่อนอื่นเราต้องตัดทุกอย่างยกเว้นหมายเลขของเรา เราจะใช้forวงวนเพื่อทำสิ่งนี้

เราต้องบอกให้ Bash shell ใช้ base 10 เพราะเลขคณิตของ Bash จะปฏิบัติกับตัวเลขที่ขึ้นต้นด้วย 0 เป็นฐาน 8 ซึ่งจะทำสิ่งต่าง ๆ ให้เรา

ในฐานะที่เป็นสคริปต์ที่จะเปิดตัวเมื่ออยู่ในไดเรกทอรีที่มีไฟล์ใช้:

#!/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

หรือคุณสามารถใช้คำสั่งนี้น่าเกลียดนานมากที่จะทำสิ่งเดียวกัน:

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

เพื่ออธิบายชิ้นส่วนทั้งหมด:

  • for f in ./* หมายความว่าทุกอย่างในไดเรกทอรีปัจจุบันทำ .... ชุดนี้แต่ละไฟล์หรือไดเรกทอรีที่พบว่าเป็นตัวแปร $ f
  • if [[ -f "$f" ]]ตรวจสอบว่ารายการที่พบเป็นไฟล์หรือไม่หากไม่เราข้ามไปที่echo "$f is not...ส่วนซึ่งหมายความว่าเราจะไม่เริ่มลบไดเรกทอรีโดยไม่ตั้งใจ
  • file="${f%.*}"กำหนดตัวแปร $ .ไฟล์เป็นชื่อไฟล์ตัดออกจากสิ่งที่เกิดขึ้นหลังจากที่ล่าสุด
  • if [[ $((10#${file##*.} % 12)) -eq 0 ]]เป็นที่ซึ่งเลขคณิตหลักเตะเข้ามา${file##*.}จดจ้องทุกอย่างก่อนนามสกุล.ในชื่อไฟล์ของเราโดยไม่มีนามสกุล $(( $num % $num2 ))เป็นไวยากรณ์สำหรับ Bash เลขคณิตเพื่อใช้การดำเนินการ modulo ใน10#ตอนเริ่มต้นบอกให้ Bash ใช้ฐาน 10 เพื่อจัดการกับ 0s ที่น่ารำคาญเหล่านั้น $((10#${file##*.} % 12))จากนั้นปล่อยให้เราเหลือชื่อไฟล์ของเราหารด้วย 12 -ne 0ตรวจสอบว่าส่วนที่เหลือคือ "ไม่เท่ากับ" ถึงศูนย์
  • หากส่วนที่เหลือไม่เท่ากับ 0 ไฟล์จะถูกลบด้วยrmคำสั่งคุณอาจต้องการแทนที่rmด้วยechoเมื่อใช้งานครั้งแรกเพื่อตรวจสอบว่าคุณได้รับไฟล์ที่คาดว่าจะลบ

โซลูชันนี้ไม่เรียกซ้ำซึ่งหมายความว่าจะประมวลผลไฟล์ในไดเรกทอรีปัจจุบันเท่านั้นจะไม่เข้าไปในไดเรกทอรีย่อยใด ๆ

ifคำสั่งกับechoคำสั่งการเตือนเกี่ยวกับไดเรกทอรีไม่จำเป็นจริงๆเป็นrmเป็นของตัวเองจะบ่นเกี่ยวกับไดเรกทอรีและไม่ลบพวกเขาดังนั้น:

#!/bin/bash

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

หรือ

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

จะทำงานอย่างถูกต้องเช่นกัน


5
การโทรrmสักสองสามพันครั้งอาจช้ามาก ฉันขอแนะนำให้echoชื่อไฟล์แทนและท่อส่งออกจากวงไป(ตัวเลือกเพิ่มตามความจำเป็น):xargs rm for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
David Foerster

ฉันได้แก้ไขเพื่อรวมการปรับปรุงความเร็วที่แนะนำของคุณแล้ว
Arronical

จริงๆแล้วหลังจากการทดสอบในไดเรกทอรีที่มีไฟล์ 55,999 รุ่นเดิมใช้เวลา 2 นาที 48 วินาทีxargsรุ่นใช้เวลา 5 นาที 1 วินาที อาจเป็นเพราะค่าใช้จ่ายในecho@DavidFoerster
Arronical

แปลก สำหรับไฟล์ 60.000 ฉันได้รับ 0m0.659s / 0m0.545s / 0m0.380s (จริง / ผู้ใช้ / sys) กับtime { for f in *; do echo "$f"; done | xargs rm; }vs. 1m11.450s / 0m10.695s / 0m16.800s ด้วยtime { for f in *; do rm "$f"; done; }tmpfs Bash คือ v4.3.11 เคอร์เนลคือ v4.4.19
David Foerster

6

คุณสามารถใช้ส่วนขยาย Bash Bracket เพื่อสร้างชื่อที่มีหมายเลข 12 ทุกตัว มาสร้างข้อมูลทดสอบกัน

$ 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

จากนั้นเราสามารถใช้สิ่งต่อไปนี้

$ 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

ทำงานช้าอย่างไร้ความหวังสำหรับไฟล์จำนวนมาก - ใช้เวลาและหน่วยความจำในการสร้างชื่อหลายพันชื่อ - ดังนั้นจึงเป็นเคล็ดลับเพิ่มเติมที่โซลูชันที่มีประสิทธิภาพจริง


ฉันชอบ code-golfing ของอันนี้
David Foerster

1

ค่อนข้างยาว แต่เป็นสิ่งที่อยู่ในใจของฉัน

 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

คำอธิบาย: ลบทุกไฟล์ที่สิบเอ็ดสิบเอ็ดครั้ง


0

ในความถ่อมใจทั้งหมดฉันคิดว่าวิธีนี้ดีกว่าคำตอบอื่น ๆ :

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

คำอธิบายเล็ก ๆ น้อย ๆ : findครั้งแรกที่เราสร้างรายการไฟล์ที่มี เราได้รับไฟล์ทั้งหมดที่มีชื่อลงท้ายด้วย.endและที่ระดับความลึก 1 (กล่าวคือไฟล์เหล่านั้นอยู่ในไดเรกทอรีการทำงานโดยตรงและไม่ได้อยู่ในโฟลเดอร์ย่อยใด ๆ คุณสามารถปล่อยให้มันอยู่หากไม่มีโฟลเดอร์ย่อย) รายการผลลัพธ์จะถูกจัดเรียงตามตัวอักษร

จากนั้นเราก็ไพพ์รายการawkที่เราใช้ตัวแปรพิเศษNRซึ่งก็คือหมายเลขบรรทัด เราจะปล่อยให้ออกทุกไฟล์ที่ 12 NR%12 != 0โดยการพิมพ์ไฟล์ที่ awkคำสั่งสามารถลงไปawk 'NR%12'เพราะผลของผู้ประกอบการโมดูโลที่ได้รับการตีความว่าเป็นค่าบูลีนและ{print}จะทำโดยปริยายอยู่แล้ว

ดังนั้นตอนนี้เรามีรายการไฟล์ที่ต้องลบซึ่งเราสามารถทำได้กับ xargs และ rm xargsรันคำสั่งที่กำหนด ( rm) ด้วยอินพุตมาตรฐานเป็นอาร์กิวเมนต์

หากคุณมีไฟล์จำนวนมากคุณจะพบข้อผิดพลาดบางอย่างเช่น 'รายการอาร์กิวเมนต์ยาวเกินไป' (บนเครื่องของฉันที่ จำกัด ที่ 256 กิโลไบต์และ POSIX ที่ต้องการขั้นต่ำคือ 4096 ไบต์) สิ่งนี้สามารถหลีกเลี่ยงได้โดยการ-n 100ตั้งค่าสถานะซึ่งแยกอาร์กิวเมนต์ขึ้นทุก ๆ 100 คำ (ไม่ใช่บรรทัดสิ่งที่ต้องระวังหากชื่อไฟล์ของคุณมีช่องว่าง) และดำเนินการrmคำสั่งแยกต่างหากแต่ละข้อมี 100 อาร์กิวเมนต์เท่านั้น


3
มีคู่ของปัญหาเกี่ยวกับวิธีการของคุณคือ: -depthจะต้องมีก่อน-name; ii) สิ่งนี้จะล้มเหลวหากชื่อไฟล์ใด ๆ มีช่องว่าง; iii) คุณกำลังสมมติว่าไฟล์จะถูกแสดงตามลำดับตัวเลข (นั่นคือสิ่งที่คุณawkทดสอบ) แต่นี่อาจจะไม่ใช่กรณีที่แน่นอน ดังนั้นสิ่งนี้จะลบชุดไฟล์แบบสุ่ม
terdon

d'โอ้! คุณพูดถูกฉันไม่ดี (แก้ไขความคิดเห็น) -depthผมได้รับข้อผิดพลาดเพราะตำแหน่งที่ผิดและจำไม่ได้ว่า สิ่งที่สำคัญที่สุดคือคุณกำลังลบชุดไฟล์แบบสุ่มและไม่ใช่ไฟล์ที่ OP ต้องการ
terdon

โอ้และ-depthไม่ไม่คิดค่าและเป็นสิ่งที่ตรงกันข้ามกับสิ่งที่คุณคิด โปรดดูman find: "-depth ประมวลผลเนื้อหาของแต่ละไดเรกทอรีก่อนหน้าไดเรกทอรี" ดังนั้นสิ่งนี้จะลงไปในไดเรกทอรีย่อยและสร้างความหายนะไปทั่วสถานที่
terdon

I) ทั้งสอง-depth nและ-maxdepth nมีอยู่ อดีตต้องการความลึกเท่ากับ n และด้วยหลังสามารถเป็น <= n II) ใช่มันไม่ดี แต่สำหรับตัวอย่างนี้ไม่มีความกังวล คุณสามารถแก้ไขได้โดยใช้find ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rmซึ่งใช้ null เป็นตัวคั่นบันทึก (ซึ่งไม่ได้รับอนุญาตในชื่อไฟล์) III) อีกครั้งในกรณีนี้สมมติฐานมีความสมเหตุสมผล มิฉะนั้นคุณสามารถแทรกsort -nระหว่างfindและawkหรือเปลี่ยนเส้นทางfindไปยังไฟล์และจัดเรียงตามที่คุณต้องการ
user593851

3
คุณอาจใช้ OSX อยู่แล้ว findนั่นคือการใช้งานที่แตกต่างกันมาก อย่างไรก็ตามอีกครั้งปัญหาหลักคือคุณสมมติว่าfindส่งคืนรายการที่เรียงลำดับ มันไม่ได้
terdon

0

สำหรับการใช้ bash เท่านั้นแนวทางแรกของฉันคือ: 1. ย้ายไฟล์ทั้งหมดที่คุณต้องการเก็บไว้ในไดเรกทอรีอื่น (เช่น. ทั้งหมดที่มีหมายเลขชื่อไฟล์คือหลาย 12) จากนั้น 2. ลบไฟล์ที่เหลือทั้งหมดในไดเรกทอรี 3. จากนั้นให้ใส่หลายไฟล์จาก 12 ไฟล์ที่คุณเก็บไว้ในที่เดิม ดังนั้นสิ่งนี้อาจใช้งานได้:

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

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