มีสองสามวิธีที่จะได้รับ tail
เพื่อออก:
วิธีการที่ไม่ดี: กำลัง tail
เพื่อเขียนบรรทัดใหม่
คุณสามารถบังคับ tail
เพื่อเขียนบรรทัดเอาต์พุตอีกบรรทัดทันทีหลังจากนั้น grep
ได้พบการแข่งขันและออก สิ่งนี้จะทำให้ tail
เพื่อรับ SIGPIPE
ทำให้มันออก วิธีหนึ่งในการทำเช่นนี้คือแก้ไขไฟล์ที่ถูกตรวจสอบโดย tail
หลังจาก grep
ทางออก
นี่คือตัวอย่างรหัส:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
ในตัวอย่างนี้ cat
จะไม่ออกจนกว่า grep
ได้ปิด stdout แล้ว tail
ไม่น่าจะสามารถเขียนไปยังไปป์ก่อน grep
ได้มีโอกาสปิด stdin แล้ว cat
ใช้เพื่อเผยแพร่เอาต์พุตมาตรฐานของ grep
ยังไม่แปร
วิธีนี้ค่อนข้างง่าย แต่มีข้อเสียหลายประการ:
- ถ้า
grep
ปิด stdout ก่อนปิด stdin จะมีเงื่อนไขการแข่งขันอยู่เสมอ: grep
ปิด stdout, ทริกเกอร์ cat
เพื่อออก echo
เรียกใช้ tail
เพื่อส่งออกบรรทัด หากสายนี้ถูกส่งไป grep
ก่อน grep
มีโอกาสที่จะปิด stdin tail
จะไม่ได้รับ SIGPIPE
จนกว่ามันจะเขียนอีกบรรทัด
- มันต้องมีการเข้าถึงการเขียนไปยังไฟล์บันทึก
- คุณต้องตกลงด้วยการแก้ไขไฟล์บันทึก
- คุณอาจเสียหายไฟล์บันทึกหากคุณเกิดการเขียนในเวลาเดียวกันกับกระบวนการอื่น (การเขียนอาจถูกอินเตอร์ลีทำให้บรรทัดใหม่ปรากฏขึ้นกลางข้อความบันทึก)
- วิธีนี้เป็นวิธีเฉพาะ
tail
- จะไม่ทำงานกับโปรแกรมอื่น ๆ
- ไปป์ไลน์ที่สามทำให้ยากต่อการเข้าถึงโค้ดส่งคืนของไปป์ไลน์ที่สอง (เว้นแต่คุณจะใช้ส่วนขยาย POSIX เช่น
bash
's PIPESTATUS
อาร์เรย์) นี่ไม่ใช่เรื่องใหญ่ในกรณีนี้เพราะ grep
จะคืนค่า 0 เสมอ แต่โดยทั่วไประยะกลางอาจถูกแทนที่ด้วยคำสั่งอื่นที่มีรหัสส่งคืนที่คุณสนใจ (เช่นสิ่งที่ส่งคืน 0 เมื่อตรวจพบ "เซิร์ฟเวอร์เริ่มต้น" 1 เมื่อตรวจพบ "เซิร์ฟเวอร์ไม่สามารถเริ่ม") .
วิธีการถัดไปหลีกเลี่ยงข้อ จำกัด เหล่านี้
แนวทางที่ดีกว่า: หลีกเลี่ยงท่อส่ง
คุณสามารถใช้ FIFO เพื่อหลีกเลี่ยงไปป์ไลน์ทั้งหมดทำให้สามารถดำเนินการได้หนึ่งครั้ง grep
ผลตอบแทน ตัวอย่างเช่น:
fifo=/tmp/tmpfifo.$$
mkfifo "${fifo}" || exit 1
tail -f logfile.log >${fifo} &
tailpid=$! # optional
grep -m 1 "Server Started" "${fifo}"
kill "${tailpid}" # optional
rm "${fifo}"
บรรทัดที่ทำเครื่องหมายด้วยความคิดเห็น # optional
สามารถลบออกได้และโปรแกรมจะยังคงทำงาน tail
จะคงอยู่จนกว่ามันจะอ่านบรรทัดอินพุตอื่นหรือถูกกระบวนการอื่นฆ่า
ข้อดีของวิธีนี้คือ:
- คุณไม่จำเป็นต้องแก้ไขไฟล์บันทึก
- วิธีนี้ใช้ได้กับยูทิลิตี้อื่น ๆ
tail
- มันไม่ได้รับผลกระทบจากสภาพการแข่งขัน
- คุณสามารถรับค่าตอบแทนได้อย่างง่ายดาย
grep
(หรือคำสั่งอื่นที่คุณใช้)
ข้อเสียของวิธีการนี้คือความซับซ้อนโดยเฉพาะอย่างยิ่งการจัดการ FIFO: คุณจะต้องสร้างชื่อไฟล์ชั่วคราวอย่างปลอดภัยและคุณจะต้องแน่ใจว่า FIFO ชั่วคราวจะถูกลบแม้ว่าผู้ใช้จะกด Ctrl-C ระหว่าง สคริปต์. สิ่งนี้สามารถทำได้โดยใช้กับดัก
Alternative Approach: ส่งข้อความถึง Kill tail
คุณสามารถรับ tail
ขั้นตอนไปป์ไลน์เพื่อออกโดยส่งสัญญาณเช่น SIGTERM
. สิ่งที่ท้าทายคือการรู้สองสิ่งในที่เดียวกันในรหัส tail
PID และไม่ว่าจะเป็น grep
ได้ออกแล้ว
ด้วยท่อเหมือน tail -f ... | grep ...
มันง่ายต่อการแก้ไขขั้นตอนการส่งไปป์ไลน์แรกที่จะบันทึก tail
PID ในตัวแปรโดยแบ็คกราวน์ tail
และการอ่าน $!
. นอกจากนี้ยังง่ายต่อการแก้ไขไปป์ไลน์ที่สองเพื่อเรียกใช้ kill
เมื่อ grep
ทางออก ปัญหาคือว่าไปป์ไลน์สองขั้นตอนทำงานใน "สภาพแวดล้อมการดำเนินการ" แยกกัน (ในคำศัพท์ของมาตรฐาน POSIX) ดังนั้นขั้นตอนไปป์ไลน์ที่สองไม่สามารถอ่านตัวแปรใด ๆ โดยไม่ใช้ตัวแปรเชลล์ขั้นตอนที่สองจะต้องคิดออก tail
PID ของมันเพื่อที่จะฆ่า tail
เมื่อ grep
ผลตอบแทนหรือขั้นตอนแรกจะต้องได้รับการแจ้งเตือนเมื่อใด grep
ผลตอบแทน
ขั้นตอนที่สองสามารถใช้ pgrep
ที่จะได้รับ tail
PID ของ แต่นั่นจะไม่น่าเชื่อถือ (คุณอาจตรงกับกระบวนการที่ไม่ถูกต้อง) และไม่ใช่พกพา ( pgrep
ไม่ได้ระบุโดยมาตรฐาน POSIX)
ขั้นตอนแรกสามารถส่ง PID ไปยังขั้นตอนที่สองผ่านทางท่อโดย echo
ไอเอ็นจี PID แต่สตริงนี้จะได้รับการผสมกับ tail
ผลลัพธ์ของ การแยกสองอย่างออกจากกันอาจต้องการรูปแบบการหลบหนีที่ซับซ้อน tail
.
คุณสามารถใช้ FIFO เพื่อให้ขั้นตอนการไพพ์ไลน์ที่สองแจ้งขั้นตอนการไพพ์ไลน์ครั้งแรกเมื่อ grep
ทางออก จากนั้นขั้นแรกก็สามารถฆ่าได้ tail
. นี่คือตัวอย่างรหัส:
fifo=/tmp/notifyfifo.$$
mkfifo "${fifo}" || exit 1
{
# run tail in the background so that the shell can
# kill tail when notified that grep has exited
tail -f logfile.log &
# remember tail's PID
tailpid=$!
# wait for notification that grep has exited
read foo <${fifo}
# grep has exited, time to go
kill "${tailpid}"
} | {
grep -m 1 "Server Started"
# notify the first pipeline stage that grep is done
echo >${fifo}
}
# clean up
rm "${fifo}"
วิธีนี้มีข้อดีข้อเสียของวิธีการก่อนหน้านี้ทั้งหมดยกเว้นมีความซับซ้อนมากขึ้น
คำเตือนเกี่ยวกับการบัฟเฟอร์
POSIX อนุญาตให้สตรีม stdin และ stdout ถูกบัฟเฟอร์เต็มซึ่งหมายความว่า tail
ผลลัพธ์ของอาจไม่ได้รับการประมวลผลโดย grep
เป็นเวลานานโดยพลการ ไม่ควรมีปัญหาใด ๆ ในระบบ GNU: GNU grep
การใช้งาน read()
ซึ่งหลีกเลี่ยงการบัฟเฟอร์ทั้งหมดและ GNU tail -f
โทรออกตามปกติ fflush()
เมื่อเขียนถึง stdout ระบบที่ไม่ใช่ของ GNU อาจต้องทำอะไรเป็นพิเศษเพื่อปิดการใช้งานหรือล้างบัฟเฟอร์เป็นประจำ