हर 12 वीं फाइल को हटा दें


14

मेरे पास फ़ाइल नाम में कुछ हजार फाइलें हैं। १३२३५।एन्ड। मैं केवल हर 12 वीं फाइल रखना चाहता हूं, इसलिए file.00012.end, file.00024.end ... file.99996.end और बाकी सबकुछ हटा दें।

फ़ाइलें उनके फ़ाइल नाम में पहले नंबर हो सकती हैं, और सामान्य रूप से हैं: file.00064.name.99999.end

मैं बैश शेल का उपयोग करता हूं और यह पता नहीं लगा सकता कि फ़ाइलों पर लूप कैसे किया जाए और फिर नंबर प्राप्त करें और जांचें कि क्या यह number%%12=0 फ़ाइल को डिलीट कर रहा है या नहीं। क्या कोई मेरी मदद कर सकता है?

धन्यवाद, डोरिना


क्या फ़ाइल का नंबर सिर्फ फ़ाइल नाम पर निर्भर है?
एरॉनिकल

इसके अलावा, क्या फ़ाइलों में हमेशा 5 अंक होते हैं, और क्या प्रत्यय और उपसर्ग हमेशा समान होते हैं?
एरॉनिकल सेप

हाँ यह हमेशा 5 अंक का होता है। मुझे यकीन नहीं है कि मुझे आपका पहला सवाल सही लगता है। विभिन्न फ़ाइल नाम वाली फाइलें अलग-अलग हैं, और मुझे इन विशिष्ट फाइलों की आवश्यकता है जो संख्या 00012, 00024 आदि हैं
डोरिना

3
@ डॉरीना कृपया अपने प्रश्न को संपादित करें और इसे स्पष्ट करें। यह सब कुछ बदल देता है!
टेराडन

2
और वे सभी एक ही निर्देशिका में हैं, है ना?
सर्गी कोलोडियाज़नी

जवाबों:


18

यहाँ एक पर्ल समाधान है। यह हजारों फ़ाइलों के लिए बहुत तेज़ होना चाहिए:

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लूप का उपयोग करेंगे ।

हमें बेस 10 का उपयोग करने के लिए बैश शेल को भी बताने की आवश्यकता है, क्योंकि बैश अंकगणित उन्हें संख्या 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 ))बैश अंकगणितीय के लिए modulo ऑपरेशन का उपयोग करने के लिए वाक्यविन्यास है, 10#शुरुआत में Bash को आधार 10 का उपयोग करने के लिए कहता है, उन pesky प्रमुख 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 --:।
डेविड फ़ॉस्टर

मैंने आपके सुझाए गए गति सुधार को शामिल करने के लिए संपादन किया है।
एरॉनिकल सेप

वास्तव में 55999 फाइलों के साथ एक निर्देशिका पर परीक्षण के बाद, मूल संस्करण ने 2mins 48secs लिया, xargsसंस्करण ने 5mins 1 सेकंड लिया। क्या यह echo@DavidFoerster पर ओवरहेड के कारण हो सकता है ?
एरोनिकल

अजीब। 60.000 फाइलों के लिए मुझे एक tmpfs के time { for f in *; do echo "$f"; done | xargs rm; }साथ 0m0.659s / 0m0.545s / 0m0.380s (वास्तविक / उपयोगकर्ता / sys) बनाम 1m11.450s / 0m10.695s / 0m16.800s के साथ मिलता है time { for f in *; do rm "$f"; done; }। बैश v4.3.11 है, कर्नेल v4.4.19 है।
डेविड फ़ॉस्टर

6

आप प्रत्येक 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

बड़ी मात्रा में फ़ाइलों के लिए उम्मीद से धीमी गति से काम करता है - हालांकि, हजारों नामों को उत्पन्न करने में समय और मेमोरी लगती है - इसलिए यह एक चाल है जो वास्तविक कुशल समाधान है।


मुझे इस पर कोड-गोल्फिंग पसंद है।
डेविड फ़ॉस्टर

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

स्पष्टीकरण: हर 12 वीं फ़ाइल को ग्यारह बार हटाएं।


0

सभी विनम्रता में मुझे लगता है कि यह समाधान अन्य उत्तर की तुलना में बहुत अच्छा है:

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

थोड़ी व्याख्या: पहले हम फाइलों की एक सूची तैयार करते हैं find। हमें वे सभी फाइलें मिलती हैं जिनका नाम 1 के साथ समाप्त होता है .endऔर जो 1 की गहराई पर हैं (यह कहना है, वे सीधे कार्यशील निर्देशिका में हैं और किसी भी सबफ़ोल्डर में नहीं हैं। यदि आप कोई सबफ़ोल्डर नहीं हैं तो आप इसे छोड़ सकते हैं)। आउटपुट सूची को वर्णानुक्रम में क्रमबद्ध किया जाएगा।

फिर हम उस सूची को पाइप करते हैं awk, जहां हम विशेष चर का उपयोग करते हैं NRजो लाइन नंबर है। हम हर 12 वीं फाइल को फाइलों को प्रिंट करके छोड़ देते हैं जहां NR%12 != 0awkआदेश को छोटा किया जा सकता awk 'NR%12'है, क्योंकि सापेक्ष ऑपरेटर का परिणाम एक बूलियन मान के रूप में व्याख्या हो जाता है और {print}परोक्ष वैसे भी किया जाता है।

तो अब हमारे पास उन फ़ाइलों की एक सूची है जिन्हें हटाने की आवश्यकता है, जिन्हें हम xargs और rm के साथ कर सकते हैं। xargsदिए गए कमांड ( rm) को तर्कों के रूप में मानक इनपुट के साथ चलाता है।

यदि आपके पास कई फाइलें हैं, तो आपको 'लॉजिक लिस्ट बहुत लंबी' (मेरी मशीन पर यह सीमा 256 kB है, और POSIX द्वारा आवश्यक न्यूनतम 4096 बाइट्स है) जैसी कुछ कहने में त्रुटि मिलेगी। इस -n 100झंडे से बचा जा सकता है , जो हर 100 शब्दों के तर्कों को विभाजित करता है (रेखाएं नहीं, कुछ देखने के लिए अगर आपकी फ़ाइल के नाम रिक्त स्थान हैं) और rmप्रत्येक 100 तर्कों के साथ एक अलग कमांड निष्पादित करता है ।


3
आपके दृष्टिकोण के साथ कुछ मुद्दे हैं: -depthपहले होने की जरूरत है -name; ii) यह विफल हो जाएगा अगर किसी भी फ़ाइल नाम में व्हॉट्सएप हो; iii) आप मान रहे हैं कि फाइलें आरोही संख्यात्मक क्रम में सूचीबद्ध होंगी (यही आपके awkलिए परीक्षण है) लेकिन यह लगभग निश्चित रूप से मामला नहीं होगा। इसलिए, यह फ़ाइलों का एक यादृच्छिक सेट हटा देगा।
टेराडन

डी 'ओह! आप काफी सही हैं, मेरा बुरा (टिप्पणी संपादित)। मुझे गलत प्लेसमेंट के कारण त्रुटि हुई और याद नहीं है -depth। फिर भी, यह सबसे कम मुद्दों में से एक था, सबसे महत्वपूर्ण यह है कि आप फ़ाइलों का एक यादृच्छिक सेट हटा रहे हैं, न कि ओपी जो चाहते हैं।
टेराडन

ओह, और नहीं, -depthएक मूल्य नहीं लेता है और यह आपके विचार के विपरीत करता है। देखें man find: "प्रत्येक निर्देशिका से पहले प्रत्येक निर्देशिका की सामग्री को संसाधित करें।" तो यह वास्तव में उपनिर्देशिका में उतरेगा और सभी जगह कहर बरपाएगा।
टेराडन

मैं) दोनों -depth nऔर -maxdepth nमौजूद हैं। पूर्व में गहराई के बिल्कुल n होने की आवश्यकता होती है, और बाद वाले के साथ यह <= n हो सकता है। द्वितीय)। हां, यह बुरा है लेकिन इस विशेष उदाहरण के लिए यह कोई चिंता की बात नहीं है। आप इसे उपयोग करके ठीक कर सकते हैं find ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm, जो नल बाइट को रिकॉर्ड विभाजक के रूप में उपयोग करता है (जिसे फ़ाइल नाम में अनुमति नहीं है)। III) एक बार फिर, इस मामले में धारणा उचित है। अन्यथा आप एक और के sort -nबीच सम्मिलित कर सकते हैं , या एक फ़ाइल पर पुनर्निर्देशित कर सकते हैं और इसे अपनी पसंद के अनुसार सॉर्ट कर सकते हैं। findawkfind
user593851 16

3
आह, आप शायद OSX का उपयोग कर रहे हैं। यह एक बहुत अलग कार्यान्वयन है find। फिर भी, मुख्य मुद्दा यह है कि आप मान रहे हैं कि findएक क्रमबद्ध सूची देता है। यह नहीं है
टेराडन

0

केवल बैश का उपयोग करने के लिए, मेरा पहला तरीका यह होगा कि: 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अगर यह सुसंगत नहीं है तो आप भाग कैसे उत्पन्न करते हैं ?
अरोनिकल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.