सबसे पुरानी फ़ाइलों को स्थानांतरित करने के लिए शेल स्क्रिप्ट?


14

मैं केवल 20 सबसे पुरानी फाइलों को एक फ़ोल्डर से दूसरे में ले जाने के लिए स्क्रिप्ट कैसे लिखूं? क्या फ़ोल्डर में सबसे पुरानी फ़ाइलों को हथियाने का एक तरीका है?


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

2
कई (सबसे?) * निक्स फाइलसिस्टम निर्माण तिथि को स्टोर नहीं करते हैं, इसलिए आप निश्चितता के साथ सबसे पुरानी फाइल को निर्धारित नहीं कर सकते हैं । आम तौर पर उपलब्ध विशेषताएँ हैं atime(अंतिम पहुंच), ctime(अंतिम अनुमति परिवर्तन), और mtime(अंतिम संशोधित) ... उदा। ls -tऔर खोजने के printf "%T" उपयोग mtime... ऐसा लगता है, के अनुसार इस लिंक , कि मेरे ext4विभाजन कर रहे हैं सक्षम एक निर्माण तिथि से निपटने में है, लेकिन lsऔर findऔर statउचित विकल्प नहीं है (अभी तक) ...
Peter.O

जवाबों:


13

के उत्पादन में पार्स lsहै विश्वसनीय नहीं

इसके बजाय, findफ़ाइलों का पता लगाने और sortटाइमस्टैम्प द्वारा उन्हें ऑर्डर करने के लिए उपयोग करें। उदाहरण के लिए:

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    # do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

यह सब क्या कर रहा है?

सबसे पहले, findकमांड वर्तमान निर्देशिका में सभी फ़ाइलों और निर्देशिकाओं का पता लगाता है ( .), लेकिन वर्तमान निर्देशिका की उपनिर्देशिकाओं में नहीं ( -maxdepth 1), फिर प्रिंट करता है:

  • एक टाइमस्टैम्प
  • एक स्थान
  • फ़ाइल के सापेक्ष पथ
  • एक पूर्ण चरित्र

टाइमस्टैम्प महत्वपूर्ण है। %T@के लिए फॉर्मेट स्पेसिफायर -printfमें नीचे टूट जाता है T, जो फ़ाइल (mtime) और "अंतिम संशोधन समय" इंगित करता है @, जो "1970 के बाद से सेकंड", आंशिक सेकंड सहित इंगित करता है।

अंतरिक्ष केवल एक मनमाना परिसीमन है। फ़ाइल का पूर्ण पथ ऐसा है जिसे हम बाद में संदर्भित कर सकते हैं, और NULL वर्ण एक टर्मिनेटर है क्योंकि यह फ़ाइल नाम में एक अवैध वर्ण है और इस प्रकार हमें यह सुनिश्चित करने के लिए देता है कि हम पथ के अंत तक पहुँच गए हैं फ़ाइल।

मैंने 2>/dev/nullइसलिए शामिल किया है कि जिन फ़ाइलों को उपयोगकर्ता को एक्सेस करने की अनुमति नहीं है, उन्हें बाहर रखा गया है, लेकिन उन्हें बाहर करने के बारे में त्रुटि संदेश दबाए जाते हैं।

findआदेश का परिणाम वर्तमान निर्देशिका में सभी निर्देशिकाओं की एक सूची है। सूची को पाइप किया जाता है, sortजिसे निम्न निर्देश दिया गया है:

  • -z न्यूलाइन की बजाए NULL को लाइन टर्मिनेटर कैरेक्टर मानें।
  • -n संख्यात्मक रूप से क्रमबद्ध करें

चूंकि सेकंड -1920 हमेशा ऊपर जाता है, हम चाहते हैं कि फ़ाइल जिसकी टाइमस्टैम्प सबसे छोटी संख्या थी। से पहला परिणाम sortसबसे छोटी संख्या वाली टाइमस्टैम्प वाली लाइन होगी। वह सब कुछ फ़ाइल नाम निकालने के लिए है।

के परिणाम find, sortपाइपलाइन को प्रक्रिया प्रतिस्थापन के माध्यम से पारित किया जाता है whileजहां इसे पढ़ा जाता है जैसे कि यह स्टडिन पर एक फ़ाइल थी। whileबदले readमें इनपुट को संसाधित करने के लिए आमंत्रित करता है।

readहम IFSकुछ भी नहीं करने के लिए चर सेट के संदर्भ में , जिसका अर्थ है कि व्हाट्सएप को अनुचित रूप से एक सीमांकक के रूप में व्याख्या नहीं किया जाएगा। readबताया जाता है -r, जो भागने के विस्तार को अक्षम करता है, और -d $'\0'जो हमारे find, sortपाइपलाइन से आउटपुट का मिलान करते हुए, अंतिम सीमांकक NULL बनाता है ।

डेटा का पहला हिस्सा, जो अपने टाइमस्टैम्प और एक स्थान से पहले के सबसे पुराने फ़ाइल पथ का प्रतिनिधित्व करता है, चर में पढ़ा जाता है line। अगला, पैरामीटर प्रतिस्थापन का उपयोग अभिव्यक्ति के साथ किया जाता है #*, जो केवल स्ट्रिंग की शुरुआत से लेकर अंतरिक्ष सहित पहली जगह तक सभी पात्रों को प्रतिस्थापित करता है, कुछ भी नहीं के साथ। यह संशोधन टाइमस्टैम्प को बंद कर देता है, जिससे फ़ाइल में केवल पूर्ण पथ रह जाता है।

इस बिंदु पर फ़ाइल का नाम संग्रहीत है $fileऔर आप इसके साथ कुछ भी कर सकते हैं। आप के साथ कुछ जब कर समाप्त कर देते हैं बयान इच्छा पाश और आदेश अगले हिस्सा और अगले फ़ाइल नाम निकालने फिर से निष्पादित किया जाएगा,।$filewhileread

वहाँ एक सरल तरीका नहीं है?

नहीं। सरल तरीके छोटी गाड़ी हैं।

यदि आप या (या कुछ भी ) का उपयोग ls -tऔर पाइप करते हैं, तो आप फ़ाइल नामों में नईलाइन्स वाली फ़ाइलों पर टूट जाएंगे। यदि आप नाम में व्हॉट्सएप के साथ फाइल करते हैं, तो इससे टूटना होगा। यदि आप नाम में नई अनुगामी के साथ फाइल करते हैं, तो इससे टूटना होगा। यदि आप बिना तो आप उनके नाम पर व्हाट्सएप के साथ फाइलों पर टूट पड़ेंगे।headtailmv $(anything)mv "$(anything)"read-d $'\0'

शायद विशिष्ट मामलों में आप यह सुनिश्चित करने के लिए जानते हैं कि एक सरल तरीका पर्याप्त है, लेकिन आपको लिपियों में कभी भी इस तरह की धारणाएं नहीं लिखनी चाहिए यदि आप ऐसा करने से बच सकते हैं।

समाधान

#!/usr/bin/env bash

# move to the first argument
dest="$1"

# move from the second argument or .
source="${2-.}"

# move the file count in the third argument or 20
limit="${3-20}"

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    echo mv "$file" "$dest"
    let limit-=1
    [[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

जैसे कॉल करें:

move-oldest /mnt/backup/ /var/log/foo/ 20

से सबसे पुरानी 20 फ़ाइलों को स्थानांतरित करने के /var/log/foo/लिए /mnt/backup/

ध्यान दें कि मैं फ़ाइलों और निर्देशिकाओं सहित हूं । फ़ाइलों के लिए केवल जोड़ने -type fके लिए findमंगलाचरण।

धन्यवाद

इस उत्तर में सुधार के लिए enzotib और Павел Танков का धन्यवाद ।


सॉर्ट का उपयोग नहीं करना चाहिए -n। कम से कम मेरे संस्करण में, यह दशमलव संख्याओं को सही ढंग से क्रमबद्ध नहीं करता है। आपको या तो तिथि में डॉट को निकालना होगा या उपयोग करना होगा -printf '%TY-%Tm-%TdT%TH:%TM:%TS %p\0' | sort -rz, आईएसओ तिथियां, या कुछ और।
l0b0

@ l0b0: यह सीमा मुझे ज्ञात है। मैं मानता हूं कि उस स्तर की ग्रेन्युलैरिटी की आवश्यकता नहीं है (अर्थात, आपको इससे परे .छांटना चाहिए।) यह कहना स्पष्ट होगा sort -z -n -t. -k1
सोरपीगल

@ l0b0: आपका समाधान एक ही बग प्रदर्शित करता है, भले ही: %TSयह भी एक "आंशिक भाग" दिखाता है, जो इस रूप में होगा 00.0000000000, इसलिए आप दानेदारता भी खो देते हैं। हाल ही में जीएनयू "संस्करण प्रकार" के लिए sortउपयोग करके इस समस्या को हल कर सकता है -V, जो इस प्रकार के फ्लोटिंग पॉइंट को उम्मीद के मुताबिक संभाल लेगा।
सोरपीगल

नहीं, क्योंकि मैं एक संख्यात्मक प्रकार के बजाय "YYYY-MM-DDThh: mm: ss" पर एक स्ट्रिंग सॉर्ट करता हूं । स्ट्रिंग प्रकार दशमलव के बारे में परवाह नहीं करता है, इसलिए इसे वर्ष 10000 तक काम करना चाहिए :)
l0b0

@ l0b0: एक स्ट्रिंग प्रकार %T@भी काम करेगा, फिर, क्योंकि यह शून्य-गद्देदार है।
सोरपीगल

4

यह zsh में सबसे आसान है, जहाँ आप Om ग्लोब क्वालीफायर का उपयोग मैच के आधार पर क्रमबद्ध करने के लिए कर सकते हैं (सबसे पुराना पहला) और [1,20]क्वालीफायर केवल पहले 20 मैचों को बनाए रखने के लिए:

mv -- *(Om[1,20]) target/

Dयदि आप डॉट फ़ाइलों को भी शामिल करना चाहते हैं तो क्वालिफायर जोड़ें । .यदि आप केवल नियमित फ़ाइलों और निर्देशिकाओं से मेल खाना चाहते हैं तो जोड़ें ।

यदि आपके पास zsh नहीं है, तो यहां एक पर्ल वन-लाइनर है (आप इसे 80 से कम अक्षरों में कर सकते हैं, लेकिन स्पष्टता में आगे के खर्च पर):

perl -e '@files = sort {-M $b <=> -M $a} glob("*"); foreach (@files[0..1]) {rename $_, "target/$_" or die "$_: $!"}'

केवल POSIX टूल या यहां तक ​​कि बैश या ksh के साथ, दिनांक के अनुसार फ़ाइलों को क्रमबद्ध करना एक दर्द है। आप इसे आसानी से कर सकते हैं ls, लेकिन आउटपुट का पार्सिंग lsसमस्याग्रस्त है, इसलिए यह केवल तभी काम करता है जब फ़ाइल नामों में केवल नई वर्णमाला के अलावा अन्य मुद्रण योग्य वर्ण हों।

ls -tr | head -n 20 | while IFS= read -r file; do mv -- "$file" target/; done

4

या के ls -tसाथ आउटपुट मिलाएं ।tailhead

सरल उदाहरण, जो केवल तभी काम करता है जब सभी फ़ाइल नामों में व्हॉट्सएप के अलावा केवल प्रिंट करने योग्य वर्ण हों और \[*?:

 mv $(ls -1tr | head -20) other_folder

1
ls -1Atr
-S

1
-1, खतरनाक। यहाँ मुझे एक उदाहरण शिल्प करते हैं: touch $'foo\n*'। यदि आप उस फ़ाइल के साथ mv "$ (ls)" निष्पादित करते हैं तो क्या होता है?
सोरपिगल

1
@Sorpigal सच में? यह कहने के लिए कमजोर है "मुझे एक उदाहरण के साथ आने दें जो आपने विशेष रूप से कहा था कि काम नहीं करेगा। अरे देखो, यह काम नहीं करता है"
माइकल Mrozek

1
@ बोरिपाल यह एक बुरा विचार नहीं है, यह 99% मामलों में काम करता है। इसका उत्तर है, "यदि आपके पास सामान्य नामों वाली फाइलें हैं, तो यह काम करता है। यदि आप एक पागल व्यक्ति हैं जो अपने फाइलनाम में नई रूपरेखाएँ एम्बेड करता है, तो यह नहीं होगा"। यही कारण है कि पूरी तरह से सही है
माइकल Mrozek

1
@MichaelMrozek: यह एक बुरा विचार है और यह बुरा है क्योंकि यह कभी-कभी विफल हो जाता है। यदि आपके पास ऐसा करने का विकल्प है जो कभी-कभी विफल हो जाता है और जो नहीं करता है, तो आपको उस विकल्प को लेना चाहिए जो नहीं करता है (और जो बुरा होता है)। जो कुछ भी आपको इंटरेक्टिव रूप से पसंद है, लेकिन एक स्क्रिप्ट फ़ाइल में करें और सलाह देते समय इसे सही तरीके से करें।
सोरपीगल

3

आप इसके लिए GNU खोज का उपयोग कर सकते हैं:

find -maxdepth 1 -type f -printf '%T@ %p\n' \
  | sort -k1,1 -g | head -20 | sed 's/^[0-9.]\+ //' \
  | xargs echo mv -t dest_dir

जहां खोज में संशोधन समय (1970 से सेकंड में) और वर्तमान निर्देशिका की प्रत्येक फ़ाइल का नाम है, आउटपुट को पहले फ़ील्ड के अनुसार क्रमबद्ध किया जाता है, 20 सबसे पुराने फ़िल्टर किए जाते हैं और उन्हें स्थानांतरित किया जाता है dest_direchoयदि आपने कमांड लाइन का परीक्षण किया है तो निकालें ।


2

किसी ने (अभी तक) एक बैश उदाहरण पोस्ट नहीं किया है जो फ़ाइल नाम में एम्बेडेड न्यूलाइन चर (एम्बेडेड कुछ भी) के लिए पूरा करता है, इसलिए यहां एक है। यह 3 सबसे पुरानी (mdate) नियमित फ़ाइलों को स्थानांतरित करता है

move=3
find . -maxdepth 1 -type f -name '*' \
 -printf "%T@\t%p\0" |sort -znk1 | { 
  while IFS= read -d $'\0' -r file; do
      printf "%s\0" "${file#*$'\t'}"
      ((--move==0)) && break
  done } |xargs -0 mv -t dest

यह परीक्षण-डेटा स्निपेट है

# make test files with names containing \n, \t and "  "
rm -f '('?[1-4]'  |?)'
for f in $'(\n'{1..4}$'  |\t)' ;do sleep .1; echo >"$f" ;done
touch -d "1970-01-01" $'(\n4  |\t)'
ls -ltr '('?[1-4]'  |'?')'; echo
mkdir -p dest

यहाँ चेक-परिणाम स्निपेट है

  ls -ltr '('?[1-4]'  |'?')'
  ls -ltr   dest/*

+1, केवल उपयोगी उत्तर से पहले मेरा (और परीक्षण डेटा रखना हमेशा अच्छा होता है।)
सोरपिगल

0

यह GNU के साथ करना सबसे आसान है find। मैं एक दिन से पुराने अपने वीडियो निगरानी प्रणाली से रिकॉर्डिंग को हटाने के लिए अपने लिनक्स डीवीआर पर हर दिन इसका उपयोग करता हूं।

यहाँ वाक्य रचना है:

find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;

याद रखें कि findनिष्पादन के समय से 24 घंटे के रूप में एक दिन को परिभाषित करता है। इसलिए 11 बजे अंतिम बार संशोधित की गई फाइलें 1 बजे डिलीट नहीं होंगी।

आप इसके findसाथ संयोजन भी कर सकते हैं cron, इसलिए विलोपन को निम्नलिखित कमांड को रूट के रूप में चलाकर स्वचालित रूप से शेड्यूल किया जा सकता है:

crontab -e << EOF
@daily /usr/bin/find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
EOF

आप हमेशा के बारे में अधिक जानकारी प्राप्त कर सकते हैं find यह मैनुअल पेज से परामर्श करके है:

man find

0

चूंकि अन्य उत्तर मेरे और प्रश्नों के उद्देश्य के अनुकूल नहीं हैं, इसलिए इस शेल का परीक्षण CentOS 7 पर किया गया है:

oldestDir=$(find /yourPath/* -maxdepth 0 -type d -printf '%T+ %p\n' | sort | head -n 1 | tr -s ' ' | cut -d ' ' -f 2)
echo "$oldestDir"
rm -rf "$oldestDir"
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.