การแยกวิเคราะห์การส่งออกของ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
และคุณสามารถทำอะไรก็ได้ที่คุณต้องการด้วย เมื่อคุณเสร็จสิ้นการทำอะไรบางอย่างกับห่วงคำสั่งพินัยกรรมและคำสั่งจะถูกดำเนินการอีกครั้งสกัดก้อนถัดไปและชื่อไฟล์ต่อไป$file
while
read
ไม่มีวิธีที่ง่ายกว่านี้หรือ
ไม่ใช่วิธีที่ง่ายกว่าคือรถบั๊กกี้
หากคุณใช้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และПавелТанковสำหรับการปรับปรุงคำตอบนี้