เชลล์สคริปต์สำหรับการย้ายไฟล์ที่เก่าที่สุด?


14

ฉันจะเขียนสคริปต์เพื่อย้ายไฟล์ที่เก่าที่สุดเพียง 20 ไฟล์จากโฟลเดอร์หนึ่งไปยังอีกโฟลเดอร์ได้อย่างไร มีวิธีที่จะคว้าไฟล์ที่เก่าแก่ที่สุดในโฟลเดอร์หรือไม่?


รวมหรือไม่รวมไดเรกทอรีย่อย? และควรจะทำซ้ำ (ในต้นไม้ไดเรกทอรี)?
maxschlepzig

2
ระบบไฟล์ nix จำนวนมาก (มากที่สุด) ไม่จัดเก็บวันที่สร้างดังนั้นคุณจึงไม่สามารถระบุไฟล์ที่เก่าที่สุดได้อย่างแน่นอน แอตทริบิวต์ที่มีให้โดยทั่วไปคือatime(การเข้าถึงล่าสุด), ctime(การเปลี่ยนแปลงการอนุญาตครั้งล่าสุด) และmtime(การแก้ไขครั้งสุดท้าย) ... เช่น ls -tและการ printf "%T"ใช้งานของ findmtime ... ดูเหมือนว่าตามลิงค์นี้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) จากนั้นพิมพ์ออกมา:

  • การประทับเวลา
  • พื้นที่
  • พา ธ ที่สัมพันธ์กับไฟล์
  • อักขระ NULL

การประทับเวลาเป็นสิ่งสำคัญ ตัว%T@ระบุรูปแบบสำหรับการ-printfหยุดพักลงTซึ่งบ่งชี้ "เวลาแก้ไขล่าสุด" ของไฟล์ (mtime) และ@ซึ่งระบุ "วินาทีตั้งแต่ 1970" รวมถึงวินาทีเศษส่วน

พื้นที่เป็นเพียงตัวคั่นโดยพลการ เส้นทางแบบเต็มไปยังไฟล์คือเพื่อให้เราสามารถอ้างอิงได้ในภายหลังและอักขระ NULL เป็นตัวยุติเนื่องจากเป็นอักขระที่ผิดกฎหมายในชื่อไฟล์และทำให้เราทราบว่าเราถึงจุดสิ้นสุดของเส้นทางไปยัง ไฟล์.

ฉันได้รวมไว้2>/dev/nullเพื่อไม่ให้มีการยกเว้นไฟล์ที่ผู้ใช้ไม่มีสิทธิ์เข้าถึง แต่ข้อความแสดงข้อผิดพลาดเกี่ยวกับการยกเว้นจะถูกระงับ

ผลลัพธ์ของfindคำสั่งคือรายการของไดเรกทอรีทั้งหมดในไดเรกทอรีปัจจุบัน รายการจะถูกไพพ์ไปsortที่คำสั่ง:

  • -z ใช้ค่า NULL เป็นอักขระตัวยกเลิกบรรทัดแทนการขึ้นบรรทัดใหม่
  • -n เรียงลำดับตัวเลข

ตั้งแต่วินาทีนับตั้งแต่ปี 1970 ขึ้นไปเรื่อย ๆ เราต้องการไฟล์ที่มีการประทับเวลาน้อยที่สุด ผลลัพธ์แรกจากsortจะเป็นบรรทัดที่มีการประทับเวลาที่เล็กที่สุด สิ่งที่เหลืออยู่ก็คือการแตกชื่อไฟล์

ผลของการfind, sortท่อจะถูกส่งผ่านทางกระบวนการทดแทนเพื่อwhileที่มันจะถูกอ่านราวกับว่ามันเป็นไฟล์บน stdin whileในทางกลับกันจะreadประมวลผลอินพุต

ในบริบทของreadเราตั้งค่าIFSตัวแปรเป็นอะไรซึ่งหมายความว่าช่องว่างจะไม่ถูกตีความอย่างไม่เหมาะสมเป็นตัวคั่น readจะบอก-rซึ่งจะปิดการขยายตัวของการหลบหนีและ-d $'\0'ซึ่งจะทำให้ปลายของเส้นคั่นโมฆะตรงกับเอาท์พุทจากเราfind, sortท่อ

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

ณ จุดนี้ชื่อไฟล์จะถูกเก็บไว้ใน$fileและคุณสามารถทำอะไรก็ได้ที่คุณต้องการด้วย เมื่อคุณเสร็จสิ้นการทำอะไรบางอย่างกับห่วงคำสั่งพินัยกรรมและคำสั่งจะถูกดำเนินการอีกครั้งสกัดก้อนถัดไปและชื่อไฟล์ต่อไป$filewhileread

ไม่มีวิธีที่ง่ายกว่านี้หรือ

ไม่ใช่วิธีที่ง่ายกว่าคือรถบั๊กกี้

หากคุณใช้ls -tและไพพ์ไปยังheadหรือtail(หรืออะไรก็ได้ ) คุณจะแตกไฟล์ที่มีบรรทัดใหม่ในชื่อไฟล์ หากคุณmv $(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วันที่ ISO หรืออย่างอื่น
l0b0

@ l0b0: ข้อ จำกัด นี้เป็นที่รู้จักกันสำหรับฉัน ผมเข้าใจว่ามันจะเพียงพอที่จะไม่จำเป็นต้องมีระดับความละเอียดที่ (นั่นคือการเรียงลำดับเกิน.จะต้องไม่เกี่ยวข้องกับคุณ.) sort -z -n -t. -k1มันจะเป็นที่ชัดเจนที่จะบอกว่า
Sorpigal

@ l0b0: วิธีการแก้ปัญหาของคุณแสดงข้อผิดพลาดเดียวกันโดยไม่คำนึงถึง: %TSยังแสดง "ส่วนที่เป็นเศษส่วน" ซึ่งจะอยู่ในรูปแบบ00.0000000000ดังนั้นคุณจึงสูญเสียความละเอียด GNU ล่าสุดsortสามารถแก้ปัญหานี้ได้โดยใช้-Vสำหรับ "การจัดเรียงเวอร์ชัน" ซึ่งจะจัดการกับจุดลอยตัวชนิดนี้ตามที่คาดไว้
Sorpigal

ไม่เพราะฉันใช้จัดเรียงสตริงใน "YYYY-MM-DDThh: mm: ss" แทนที่จะเรียงลำดับตัวเลข String เรียงลำดับไม่สนใจเกี่ยวกับทศนิยมดังนั้นจึงควรทำงานจนกว่าจะถึงปี 10000 :)
l0b0

@ l0b0: การเรียงลำดับสตริง%T@จะทำงานได้เช่นกันเพราะมันเป็นศูนย์
Sorpigal

4

เป็นเรื่องง่ายที่สุดใน zsh ซึ่งคุณสามารถใช้ตัวระบุแบบOm กลมเพื่อเรียงลำดับการจับคู่ตามวันที่ (เก่าที่สุดก่อน) และตัวระบุ[1,20]เพื่อรักษาเฉพาะการจับคู่ 20 อันดับแรก:

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

เพิ่มDqualifier หากคุณต้องการรวมไฟล์ dot ด้วย เพิ่ม.ถ้าคุณต้องการจับคู่เฉพาะไฟล์ปกติและไม่ใช่ไดเรกทอรี

หากคุณไม่มี zsh นี่คือ Perl one-liner (คุณสามารถทำได้น้อยกว่า 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
เพิ่มตัวเลือก -A เพื่อ ls:ls -1Atr
Arcege

1
-1, อันตราย ที่นี่ให้ฉันสร้างตัวอย่าง: touch $'foo\n*'. จะเกิดอะไรขึ้นถ้าคุณเรียกใช้ mv "$ (ls)" กับไฟล์นั้นที่นั่น?
Sorpigal

1
@Sorpigal อย่างจริงจัง? เป็นชนิดอ่อนแอที่จะพูดว่า "ให้ฉันมากับตัวอย่างที่คุณบอกว่าไม่ทำงานเฮ้ดูมันไม่ทำงาน"
Michael Mrozek

1
@Sorpigal มันไม่ได้เป็นความคิดที่ดีมันทำงานได้ใน 99% ของกรณี คำตอบคือ "ถ้าคุณมีไฟล์ที่มีชื่อปกติมันใช้งานได้ถ้าคุณเป็นคนบ้าที่ฝังบรรทัดใหม่ในชื่อไฟล์ของพวกเขา ถูกต้องครบถ้วน
Michael Mrozek

1
@MichaelMrozek: มันเป็นความคิดที่ไม่ดีและมันก็ไม่ดีเพราะบางครั้งมันก็ล้มเหลว หากคุณมีตัวเลือกในการทำสิ่งที่ล้มเหลวบางครั้งและสิ่งที่ไม่คุณควรใช้ตัวเลือกที่ไม่ (และสิ่งที่ไม่ดี) ทำสิ่งที่คุณชอบแบบโต้ตอบ แต่ในไฟล์สคริปต์และเมื่อให้คำแนะนำทำอย่างถูกต้อง
Sorpigal

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_dirจะถูกกรองและย้ายไป ลบechoถ้าคุณได้ทดสอบบรรทัดคำสั่ง


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, คำตอบที่มีประโยชน์เท่านั้นก่อนฉัน (และมันก็ดีเสมอที่จะมีข้อมูลการทดสอบ)
Sorpigal

0

มันเป็นเรื่องที่ง่ายที่สุดที่จะทำอย่างไรกับ findGNU ฉันใช้มันทุกวันบน Linux DVR ของฉันเพื่อลบการบันทึกจากระบบเฝ้าระวังวิดีโอที่เก่ากว่าวัน

นี่คือไวยากรณ์:

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

โปรดจำไว้ว่าfindกำหนดวันละ 24 ชั่วโมงนับจากเวลาของการดำเนินการ ดังนั้นไฟล์ที่แก้ไขล่าสุดเมื่อเวลา 23.00 น. จะไม่ถูกลบในเวลา 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"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.