ในเชลล์ฉันtail
จะสร้างไฟล์ล่าสุดในไดเรกทอรีได้อย่างไร?
ในเชลล์ฉันtail
จะสร้างไฟล์ล่าสุดในไดเรกทอรีได้อย่างไร?
คำตอบ:
ไม่ได้แยกการส่งออกของคำสั่ง ls! การแยกวิเคราะห์การส่งออกของ LS เป็นเรื่องยากและไม่น่าเชื่อถือ
หากคุณต้องทำสิ่งนี้ฉันแนะนำให้ใช้ find เดิมทีฉันมีตัวอย่างง่ายๆที่นี่เพื่อให้คุณได้รับส่วนสำคัญของการแก้ปัญหา แต่เนื่องจากคำตอบนี้ค่อนข้างเป็นที่นิยมฉันตัดสินใจที่จะแก้ไขเพื่อให้รุ่นที่ปลอดภัยในการคัดลอก / วางและใช้กับอินพุตทั้งหมด คุณนั่งสบายไหม? เราจะเริ่มต้นด้วย oneliner ที่จะให้ไฟล์ล่าสุดในไดเรกทอรีปัจจุบัน:
tail -- "$(find . -maxdepth 1 -type f -printf '%T@.%p\0' | sort -znr -t. -k1,2 | while IFS= read -r -d '' -r record ; do printf '%s' "$record" | cut -d. -f3- ; break ; done)"
ไม่ใช่ตอนนี้เลยใช่ไหม? ที่นี่เป็นอีกครั้งในรูปแบบฟังก์ชั่นของเชลล์และจัดรูปแบบเพื่อให้อ่านง่ายขึ้น
latest-file-in-directory () {
find "${@:-.}" -maxdepth 1 -type f -printf '%T@.%p\0' | \
sort -znr -t. -k1,2 | \
while IFS= read -r -d '' -r record ; do
printf '%s' "$record" | cut -d. -f3-
break
done
}
และตอนนี้ที่เป็น oneliner นี้:
tail -- "$(latest-file-in-directory)"
หากสิ่งอื่นล้มเหลวคุณสามารถรวมฟังก์ชั่นด้านบนในของคุณ.bashrc
และพิจารณาปัญหาที่แก้ไขได้ด้วยหนึ่งข้อแม้ ถ้าคุณแค่อยากให้งานเสร็จคุณไม่จำเป็นต้องอ่านเพิ่มเติม
ข้อแม้นี้คือชื่อไฟล์ที่ลงท้ายด้วยหนึ่งบรรทัดขึ้นไปจะยังไม่ถูกส่งผ่านไปยังtail
อย่างถูกต้อง การแก้ไขปัญหานี้ซับซ้อนและฉันคิดว่าเพียงพอว่าหากพบชื่อไฟล์ที่เป็นอันตรายดังกล่าวพฤติกรรมที่ค่อนข้างปลอดภัยในการพบข้อผิดพลาด "ไม่มีไฟล์ดังกล่าว" จะเกิดขึ้นแทนสิ่งที่เป็นอันตรายมากกว่า
สำหรับคนที่อยากรู้อยากเห็นนี่เป็นคำอธิบายที่น่าเบื่อว่ามันทำงานยังไงทำไมมันถึงปลอดภัย
ก่อนอื่นไบต์เดียวที่มีความปลอดภัยในการกำหนดเส้นทางไฟล์นั้นเป็นโมฆะเพราะเป็นสิ่งเดียวที่ถูกห้ามในระดับสากลในเส้นทางไฟล์บนระบบ Unix เป็นสิ่งสำคัญเมื่อจัดการรายการพา ธ ไฟล์ใด ๆ เพื่อใช้ null เป็นตัวคั่นเท่านั้นและเมื่อส่งพา ธ ไฟล์เดียวจากโปรแกรมหนึ่งไปยังอีกโปรแกรมหนึ่งให้ทำในลักษณะที่จะไม่ทำให้เกิดปัญหากับไบต์ที่กำหนดเอง มีหลายวิธีที่ดูเหมือนถูกต้องในการแก้ปัญหานี้และปัญหาอื่น ๆ ที่ล้มเหลวโดยสมมติว่า (โดยไม่ตั้งใจ) ว่าชื่อไฟล์จะไม่มีบรรทัดใหม่หรือช่องว่างในนั้น ข้อสมมติฐานทั้งสองไม่มีความปลอดภัย
สำหรับจุดประสงค์ของวันนี้ขั้นตอนที่หนึ่งคือการรับรายการแฟ้มที่ไม่มีการคั่น มันค่อนข้างง่ายถ้าคุณมีการfind
สนับสนุน-print0
เช่น GNU's:
find . -print0
แต่รายการนี้ยังไม่ได้บอกเราว่ารายการใดเป็นรุ่นล่าสุดดังนั้นเราจึงจำเป็นต้องรวมข้อมูลนั้น ฉันเลือกที่จะใช้-printf
สวิตช์ของ find ซึ่งให้ฉันระบุข้อมูลที่ปรากฏในผลลัพธ์ ไม่ได้ทุกรุ่นของfind
การสนับสนุน-printf
(มันไม่ได้มาตรฐาน) แต่ GNU หาไม่ หากคุณพบว่าตัวเองไม่มี-printf
คุณจะต้องพึ่งพาใน-exec stat {} \;
จุดที่คุณต้องละทิ้งความหวังของการพกพาทั้งหมดที่stat
ไม่ได้มาตรฐาน ตอนนี้ฉันจะเดินหน้าต่อไปสมมติว่าคุณมีเครื่องมือของ GNU
find . -printf '%T@.%p\0'
ที่นี่ฉันขอรูปแบบ printf %T@
ซึ่งเป็นเวลาในการแก้ไขในไม่กี่วินาทีนับตั้งแต่จุดเริ่มต้นของยุค Unix ตามด้วยระยะเวลาและจากนั้นตามด้วยตัวเลขที่แสดงเศษส่วนของวินาที ฉันเพิ่มในช่วงเวลาอื่นแล้ว%p
(ซึ่งเป็นเส้นทางแบบเต็มไปยังไฟล์) ก่อนที่จะลงท้ายด้วยไบต์ว่าง
ตอนนี้ฉันมี
find . -maxdepth 1 \! -type d -printf '%T@.%p\0'
มันอาจจะไปโดยไม่บอก แต่เพื่อประโยชน์ของการเป็นที่สมบูรณ์-maxdepth 1
ป้องกันfind
จากรายชื่อเนื้อหาของไดเรกทอรีย่อยและข้ามไดเรกทอรีที่คุณไม่น่าจะต้องการที่จะ\! -type d
tail
จนถึงตอนนี้ฉันมีไฟล์ในไดเรกทอรีปัจจุบันที่มีข้อมูลเวลาการแก้ไขดังนั้นตอนนี้ฉันต้องเรียงลำดับตามเวลาการแก้ไขนั้น
โดยค่าเริ่มต้นsort
คาดว่าอินพุตจะเป็นระเบียนที่คั่นด้วยบรรทัดใหม่ หากคุณมี GNU sort
คุณสามารถขอให้คาดหวังว่าจะมีระเบียนที่คั่นด้วย null แทนโดยใช้-z
สวิตช์ สำหรับมาตรฐานsort
ไม่มีวิธีแก้ปัญหา ฉันสนใจเรียงลำดับตามตัวเลขสองตัวแรกเท่านั้น (ไม่กี่วินาทีและเสี้ยววินาที) และไม่ต้องการเรียงตามชื่อไฟล์จริงดังนั้นฉันจึงบอกsort
สองสิ่ง: อันดับแรกควรพิจารณาจุด ( .
) ตัวคั่นฟิลด์ และที่สองที่ควรใช้เฉพาะเขตข้อมูลแรกและเขตข้อมูลที่สองเมื่อพิจารณาวิธีการเรียงลำดับระเบียน
| sort -znr -t. -k1,2
ก่อนอื่นฉันรวมสามตัวเลือกสั้น ๆ ที่ไม่มีค่าด้วยกัน -znr
เป็นเพียงวิธีรัดกุมในการพูด-z -n -r
) หลังจากนั้น-t .
(ช่องว่างเป็นทางเลือก) จะบอกsort
ถึงตัวคั่นเขตข้อมูลและ-k 1,2
ระบุหมายเลขเขตข้อมูล: ครั้งแรกและครั้งที่สอง ( sort
นับเขตข้อมูลจากที่หนึ่งไม่ใช่ศูนย์) โปรดจำไว้ว่าตัวอย่างระเบียนสำหรับไดเรกทอรีปัจจุบันจะมีลักษณะดังนี้:
1000000000.0000000000../some-file-name
หมายถึงsort
จะดูก่อน1000000000
จากนั้น0000000000
เมื่อสั่งซื้อบันทึกนี้ -n
ตัวเลือกที่บอกsort
จะใช้การเปรียบเทียบตัวเลขเมื่อเปรียบเทียบค่าเหล่านี้เพราะทั้งสองค่าเป็นตัวเลข สิ่งนี้อาจไม่สำคัญเนื่องจากตัวเลขมีความยาวคงที่ แต่ไม่เป็นอันตราย
สวิทช์อื่น ๆ ที่กำหนดให้sort
เป็น-r
สำหรับ "ย้อนกลับ". ตามค่าดีฟอลต์เอาต์พุตของการเรียงลำดับตัวเลขจะเป็นตัวเลขที่ต่ำที่สุดก่อน-r
ทำการเปลี่ยนแปลงเพื่อให้แสดงรายการตัวเลขต่ำสุดที่ผ่านมาและตัวเลขสูงสุดก่อน เนื่องจากตัวเลขเหล่านี้มีการประทับเวลาสูงกว่าจะหมายถึงใหม่กว่าและนี่จะเป็นการบันทึกใหม่ล่าสุดที่จุดเริ่มต้นของรายการ
เมื่อรายการเส้นทางของไฟล์โผล่ออกมาในsort
ขณะนี้จะมีคำตอบที่ต้องการซึ่งเรากำลังมองหาที่ด้านบนสุด สิ่งที่เหลืออยู่คือการหาวิธีที่จะละทิ้งระเบียนอื่น ๆ และตัดการประทับเวลา น่าเสียดายที่แม้แต่ GNU head
และtail
ไม่ยอมรับสวิตช์เพื่อให้สามารถทำงานกับอินพุตที่ไม่มีการ จำกัด head
แต่ผมใช้ในขณะที่ห่วงเป็นชนิดของชายคนยากจน
| while IFS= read -r -d '' record
ก่อนอื่นฉันไม่ได้ตั้งค่าIFS
เพื่อให้รายการไฟล์ไม่อยู่ภายใต้การแยกคำ ถัดไปฉันบอกread
สองสิ่ง: อย่าตีความ escape sequences ในอินพุต ( -r
) และอินพุตถูกคั่นด้วย null null ( -d
); ที่นี่สตริงที่ว่าง''
จะใช้ในการระบุ "ไม่มีตัวคั่น" aka คั่นด้วยโมฆะ แต่ละเร็กคอร์ดจะถูกอ่านเข้ากับตัวแปรrecord
เพื่อให้แต่ละครั้งwhile
วนซ้ำวนซ้ำมันจะมีการประทับเวลาเดียวและชื่อไฟล์เดียว โปรดทราบว่า-d
เป็นส่วนขยายของ GNU หากคุณมีเพียงมาตรฐานread
เทคนิคนี้จะไม่ทำงานและคุณมีสิทธิ์เพียงเล็กน้อย
เรารู้ว่าrecord
ตัวแปรมีสามส่วนโดยคั่นด้วยอักขระแบบจุด การใช้cut
ยูทิลิตีเป็นไปได้ที่จะแยกส่วนของพวกเขา
printf '%s' "$record" | cut -d. -f3-
ที่นี่มีการส่งเร็กคอร์ดทั้งหมดไปยังprintf
และจากที่นั่นไปยังcut
; ในทุบตีคุณสามารถลดความซับซ้อนนี้ต่อไปโดยใช้สตริงที่นี่เพื่อcut -d. -3f- <<<"$record"
ประสิทธิภาพที่ดีขึ้น เราบอกcut
สองสิ่ง: สิ่งแรก-d
คือมันควรมีตัวคั่นเฉพาะสำหรับการระบุเขตข้อมูล (เช่นเดียวกับที่sort
ใช้ตัวคั่น.
) ประการที่สองcut
คือการสั่ง-f
ให้พิมพ์เฉพาะค่าจากฟิลด์เฉพาะ รายการเขตข้อมูลจะได้รับเป็นช่วง3-
ที่ระบุค่าจากเขตข้อมูลที่สามและจากเขตข้อมูลต่อไปนี้ทั้งหมด ซึ่งหมายความว่าcut
จะอ่านและไม่สนใจทุกอย่างจนถึงและรวมถึงสิ่งที่สอง.
ที่พบในเรกคอร์ดและจากนั้นจะพิมพ์ส่วนที่เหลือซึ่งเป็นส่วนของพา ธ ไฟล์
การพิมพ์เส้นทางไฟล์ใหม่ล่าสุดไม่จำเป็นต้องดำเนินต่อไป: break
ออกจากลูปโดยไม่ปล่อยให้มันย้ายไปยังพา ธ ไฟล์ที่สอง
สิ่งเดียวที่ยังคงทำงานtail
อยู่บนเส้นทางของไฟล์ที่ส่งคืนโดยไพพ์ไลน์นี้ คุณอาจสังเกตเห็นในตัวอย่างของฉันว่าฉันได้ทำสิ่งนี้โดยใส่ไปป์ไลน์ใน subshell สิ่งที่คุณอาจไม่ได้สังเกตก็คือฉันใส่ subshell ในเครื่องหมายคำพูดคู่ สิ่งนี้มีความสำคัญเนื่องจากในที่สุดแม้จะมีความพยายามทั้งหมดที่จะปลอดภัยสำหรับชื่อไฟล์ใดก็ตามการขยาย subshell ที่ไม่ได้กล่าวถึงยังอาจทำให้สิ่งต่าง ๆ เสียหายได้ มีคำอธิบายเพิ่มเติมโดยละเอียดหากคุณสนใจ สิ่งที่สำคัญที่สอง แต่มองข้ามไปได้อย่างง่ายดายจากการเรียกใช้tail
คือฉันได้จัดเตรียมตัวเลือก--
ไว้ก่อนที่จะขยายชื่อไฟล์ นี้จะแนะนำtail
-
ว่าไม่มีตัวเลือกมากขึ้นมีการระบุและทุกอย่างต่อไปนี้เป็นชื่อไฟล์ซึ่งจะทำให้มันปลอดภัยที่จะจัดการกับชื่อไฟล์ที่ขึ้นต้นด้วย
tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
head
และtail
ไม่ยอมรับสวิตช์เพื่อให้สามารถทำงานกับอินพุตที่มีการ จำกัด ด้วย null ได้" การแทนที่ของฉันสำหรับhead
: … | grep -zm <number> ""
.
tail `ls -t | head -1`
หากคุณกังวลเรื่องชื่อไฟล์ที่มีช่องว่าง
tail "`ls -t | head -1`"
คุณสามารถใช้ได้:
tail $(ls -1t | head -1)
การ$()
สร้างจะเริ่มเชลล์ย่อยซึ่งรันคำสั่งls -1t
(แสดงรายการไฟล์ทั้งหมดตามลำดับเวลาหนึ่งรายการต่อบรรทัด) และไพพ์นั้นผ่านhead -1
เพื่อรับบรรทัดแรก (ไฟล์)
เอาต์พุตของคำสั่งนั้น (ไฟล์ล่าสุด) จะถูกส่งไปtail
ยังเพื่อประมวลผล
โปรดทราบว่าสิ่งนี้จะเสี่ยงต่อการได้รับไดเรกทอรีหากเป็นรายการไดเรกทอรีล่าสุดที่สร้างขึ้น ฉันใช้เคล็ดลับในนามแฝงเพื่อแก้ไขไฟล์บันทึกล่าสุด (จากชุดการหมุน) ในไดเรกทอรีที่มีเฉพาะไฟล์บันทึกเหล่านั้น
-1
ไม่จำเป็นls
ไม่ว่าสำหรับคุณเมื่อมันอยู่ในท่อ เปรียบเทียบls
และls|cat
ตัวอย่างเช่น
บนระบบ POSIX ไม่มีวิธีรับรายการไดเร็กทอรี "ที่สร้างล่าสุด" รายการไดเรกทอรีแต่ละคนมีatime
, mtime
และctime
แต่ตรงกันข้ามกับ Microsoft Windows ที่ctime
ไม่ CreationTime เฉลี่ย แต่ "เวลาของการเปลี่ยนแปลงสถานะล่าสุด"
ดังนั้นสิ่งที่ดีที่สุดที่คุณจะได้รับคือ "ปรับไฟล์ล่าสุดที่แก้ไขล่าสุด" ซึ่งอธิบายไว้ในคำตอบอื่น ๆ ฉันต้องการคำสั่งนี้:
tail -f "$ (ls -tr | sed 1q)"
หมายเหตุเครื่องหมายคำพูดรอบls
คำสั่ง สิ่งนี้ทำให้ตัวอย่างทำงานกับชื่อไฟล์เกือบทั้งหมด
ฉันแค่ต้องการเห็นการเปลี่ยนแปลงขนาดไฟล์ที่คุณสามารถใช้ดู
watch -d ls -l
ในzsh
:
tail *(.om[1])
โปรดดู: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiersที่นี่m
แสดงถึงเวลาในการแก้ไขm[Mwhms][-|+]n
และวิธีการก่อนหน้านี้o
จะเรียงลำดับแบบเดียว ( O
เรียงลำดับแบบอื่น) .
หมายถึงเฉพาะไฟล์ปกติ ภายในวงเล็บ[1]
เลือกรายการแรก ที่จะเลือกสามการใช้งานจะได้รับการใช้งานที่เก่าแก่ที่สุด[1,3]
[-1]
ls
มันสั้นที่ดีและไม่ได้ใช้
อาจมีหลายล้านวิธีในการทำเช่นนี้ แต่วิธีที่ฉันจะทำคือ:
tail `ls -t | head -n 1`
บิตระหว่าง backticks (เครื่องหมายคำพูดเหมือนตัวอักษร) จะถูกตีความและผลลัพธ์จะถูกส่งกลับไปยังส่วนท้าย
ls -t #gets the list of files in time order
head -n 1 # returns the first line only
ง่าย ๆ :
tail -f /path/to/directory/*
ทำงานได้ดีสำหรับฉัน
ปัญหาคือการรับไฟล์ที่สร้างขึ้นหลังจากที่คุณเริ่มคำสั่ง tail แต่ถ้าคุณไม่ต้องการ (เช่นเดียวกับวิธีแก้ปัญหาทั้งหมดข้างต้นไม่สนใจ) เครื่องหมายดอกจันเป็นเพียงวิธีที่ง่ายกว่า IMO
มีคนโพสต์แล้วลบมันด้วยเหตุผลบางอย่าง แต่นี่เป็นสิ่งเดียวที่ทำงานได้ดังนั้น ...
tail -f `ls -tr | tail`
ls
ไม่ใช่สิ่งที่ฉลาดที่สุดในการทำ ...
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
คำอธิบาย:
tail "$(ls -1tr|tail -1)"