การแยกวิเคราะห์การส่งออกของ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และПавелТанковสำหรับการปรับปรุงคำตอบนี้