ตรวจสอบไฟล์จนกว่าจะพบสตริง


49

ฉันใช้ tail -f เพื่อตรวจสอบล็อกไฟล์ที่กำลังเขียนอยู่ เมื่อมีการเขียนสตริงที่แน่นอนไปยังล็อกไฟล์ฉันต้องการออกจากการตรวจสอบและดำเนินการต่อกับสคริปต์ที่เหลือ

ขณะนี้ฉันกำลังใช้:

tail -f logfile.log | grep -m 1 "Server Started"

เมื่อพบสตริง grep จะหยุดทำงานตามที่คาดไว้ แต่ฉันต้องการค้นหาวิธีที่จะทำให้คำสั่ง tail หยุดทำงานเช่นกัน


ฉันสงสัยว่าระบบปฏิบัติการใดที่โปสเตอร์ดั้งเดิมกำลังทำงานอยู่ บนระบบ Linux RHEL5 ฉันรู้สึกประหลาดใจที่พบว่าคำสั่ง tail จะตายเมื่อคำสั่ง grep พบการแข่งขันและออก
ZaSter

3
@ZaSter: tail ตายที่บรรทัดถัดไปเท่านั้น ลองสิ่งนี้: date > log; tail -f log | grep -m 1 trigger และจากนั้นในเปลือกอื่น: echo trigger >> log และคุณจะเห็นผลลัพธ์ trigger ในเชลล์แรก แต่ไม่มีการยกเลิกคำสั่ง จากนั้นลอง: date >> log ในเปลือกที่สองและคำสั่งในเปลือกแรกจะยุติ แต่บางครั้งมันก็สายเกินไป เราต้องการที่จะยุติโดยเร็วที่สุดเมื่อมีสายเรียกขึ้นมาไม่ใช่เมื่อสายหลังจากสายเรียกนั้นเสร็จสมบูรณ์
Alfe

นั่นเป็นคำอธิบายและตัวอย่างที่ยอดเยี่ยม @Alfe
ZaSter

1
โซลูชันที่แข็งแกร่งแบบบรรทัดเดียวที่หรูหราคือการ ใช้ tail + grep -q ชอบคำตอบของ 00prometheus
Trevor Boyd Smith

คำตอบ:


24

POSIX ง่าย ๆ แบบหนึ่งซับ

นี่คือหนึ่งซับง่าย ไม่จำเป็นต้องใช้เทคนิคเฉพาะทุบตีหรือไม่ใช่ POSIX หรือแม้แต่ไปป์ที่มีชื่อ สิ่งที่คุณต้องการจริงๆคือการแยกการเลิกจ้าง tail จาก grep. ด้วยวิธีนี้ครั้งเดียว grep สิ้นสุดสคริปต์สามารถดำเนินต่อไปแม้ว่า tail ยังไม่สิ้นสุด ดังนั้นวิธีการง่ายๆนี้จะพาคุณไปที่นั่น:

( tail -f -n0 logfile.log & ) | grep -q "Server Started"

grep จะบล็อกจนกว่าจะพบสตริงจากนั้นมันจะออก โดยการทำ tail เรียกใช้จาก sub-shell ของตัวเองเราสามารถวางไว้ในพื้นหลังเพื่อให้ทำงานอย่างอิสระ ในขณะเดียวกันเชลล์หลักมีอิสระในการเรียกใช้สคริปต์ต่อทันที grep ทางออก tail จะยังคงอยู่ในเชลล์ย่อยจนกว่าบรรทัดถัดไปจะถูกเขียนไปยังล็อกไฟล์แล้วออก (อาจเป็นไปได้แม้หลังจากสคริปต์หลักได้ยกเลิก) ประเด็นหลักคือท่อไม่รออีกต่อไป tail เพื่อยุติดังนั้นไปป์ไลน์จึงออกโดยเร็วที่สุด grep ทางออก

การปรับแต่งเล็กน้อย:

  • ตัวเลือก -n0 ถึง tail ทำให้มันเริ่มอ่านจากบรรทัดสุดท้ายของไฟล์บันทึกในกรณีที่สตริงมีอยู่ก่อนหน้าในล็อกไฟล์
  • คุณอาจต้องการที่จะให้ tail -F แทน -f ไม่ใช่ POSIX แต่อนุญาต tail เพื่อทำงานแม้ว่าบันทึกจะถูกหมุนในขณะที่รอ
  • ตัวเลือก -q มากกว่า -m1 ทำให้ grep ออกหลังจากเกิดครั้งแรก แต่ไม่มีการพิมพ์บรรทัดเรียก นอกจากนี้ยังเป็น POSIX ซึ่ง -m1 ไม่ใช่

2
วิธีการนี้จะทำให้ tail ทำงานในพื้นหลังตลอดไป คุณจะจับภาพอย่างไร tail PID ภายใน sub-shell ย่อยและแสดงว่าเป็นเชลล์หลักหรือไม่ ฉันสามารถแก้ไขปัญหาเฉพาะหน้าย่อยเท่านั้นโดยฆ่าทุกเซสชันที่แนบมา tail กระบวนการโดยใช้ pkill -s 0 tail.
Rick van der Zwet

ในกรณีส่วนใหญ่จะไม่เป็นปัญหา เหตุผลที่คุณทำเช่นนี้ในตอนแรกก็เพราะคุณคาดว่าจะมีการเขียนบรรทัดเพิ่มเติมลงในไฟล์บันทึก tail จะยุติลงทันทีที่พยายามเขียนลงในไปป์ที่ขาด ท่อจะแตกทันที grep เสร็จสิ้นแล้วดังนั้นหนึ่งครั้ง grep เสร็จสิ้น tail จะยุติลงหลังจากไฟล์บันทึกได้รับอีกหนึ่งบรรทัด
00prometheus

เมื่อฉันใช้วิธีนี้ฉันทำ ไม่ พื้นหลัง tail -f.
Trevor Boyd Smith

2
@ Trevor Boyd Smith ใช่แล้วที่ใช้งานได้ในสถานการณ์ส่วนใหญ่ แต่ปัญหา OP คือ grep จะไม่ทำงานจนกว่า tail จะหยุดทำงานและ tail จะไม่หยุดจนกว่าจะมีบรรทัดอื่นปรากฏในล็อก หลังจาก grep เลิกแล้ว (เมื่อหางพยายามให้อาหารไปป์ไลน์ที่ถูกทำลายโดยการจบ grep) ดังนั้นหากคุณไม่วางพื้นหลังสคริปต์ของคุณจะไม่ทำงานต่อไปจนกว่าจะมีบรรทัดเพิ่มเติมปรากฏในไฟล์บันทึกแทนที่จะเป็นบรรทัดที่ grep จับ
00prometheus

เรื่อง "ทางจะไม่หยุดจนกว่าจะมีอีกบรรทัดหนึ่งปรากฏขึ้นหลังจาก [รูปแบบสตริงที่ต้องการ]": นั่นฉลาดมากและฉันพลาดไปโดยสิ้นเชิง ฉันไม่ได้สังเกตเพราะรูปแบบที่ฉันมองหาอยู่ตรงกลางและพิมพ์ออกมาอย่างรวดเร็ว (พฤติกรรมที่คุณอธิบายอีกครั้งนั้นบอบบางมาก)
Trevor Boyd Smith

54

คำตอบที่ได้รับการยอมรับใช้งานไม่ได้สำหรับฉันรวมถึงความสับสนและเปลี่ยนล็อกไฟล์

ฉันกำลังใช้สิ่งนี้:

tail -f logfile.log | while read LOGLINE
do
   [[ "${LOGLINE}" == *"Server Started"* ]] && pkill -P $$ tail
done

หากบรรทัดบันทึกตรงกับรูปแบบให้ฆ่า tail เริ่มโดยสคริปต์นี้

หมายเหตุ: หากคุณต้องการดูผลลัพธ์บนหน้าจอเช่นกัน | tee /dev/tty หรือ echo บรรทัดก่อนทดสอบในขณะที่ลูป


2
มันใช้งานได้ แต่ pkill ไม่ได้ระบุโดย POSIX และไม่สามารถใช้ได้ทุกที่
Richard Hansen

2
คุณไม่ต้องการสักครู่ ใช้การเฝ้าดูด้วยตัวเลือก -g และคุณสามารถสำรองคำสั่ง pkill ที่น่ารังเกียจ
l1zard

@ l1zard คุณจะเอามันออกมาได้ไหม? คุณจะดูส่วนท้ายของล็อกไฟล์อย่างไรจนกระทั่งมีบรรทัดใดบรรทัดหนึ่งปรากฏขึ้นมา? (สำคัญน้อยกว่า แต่ฉันยังอยากรู้อยากเห็นเมื่อเพิ่ม -g watch ฉันมีเซิร์ฟเวอร์ Debian รุ่นใหม่ที่มีตัวเลือกนั้นและอีกตัวที่ใช้ RHEL ตัวเก่าที่ไม่มี)
Rob Whelan

มันค่อนข้างชัดเจนสำหรับฉันว่าทำไมถึงจำเป็นหางที่นี่ เท่าที่ฉันเข้าใจถูกต้องผู้ใช้ต้องการรันคำสั่งเฉพาะเมื่อคำหลักบางอย่างในไฟล์บันทึกปรากฏ คำสั่งที่ให้ไว้ด้านล่างโดยใช้การเฝ้าดูทำได้ดีมาก
l1zard

ไม่มาก - เป็นการตรวจสอบเมื่อมีสตริงที่กำหนด ที่เพิ่ม ไปยังไฟล์บันทึก ฉันใช้สิ่งนี้เพื่อตรวจสอบเมื่อ Tomcat หรือ JBoss เริ่มต้นอย่างสมบูรณ์ พวกเขาเขียน "เซิร์ฟเวอร์เริ่มต้น" (หรือคล้ายกัน) ในแต่ละครั้งที่เกิดขึ้น
Rob Whelan

14

มีสองสามวิธีที่จะได้รับ 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 อาจต้องทำอะไรเป็นพิเศษเพื่อปิดการใช้งานหรือล้างบัฟเฟอร์เป็นประจำ


วิธีแก้ปัญหาของคุณ (เช่นคนอื่น ๆ ฉันจะไม่โทษคุณ) จะคิดถึงสิ่งที่เขียนไปยังล็อกไฟล์ก่อนที่การตรวจสอบของคุณจะเริ่มขึ้น tail -f จะส่งออกเพียงสิบบรรทัดสุดท้ายจากนั้นตามทั้งหมดต่อไปนี้ เพื่อปรับปรุงสิ่งนี้คุณสามารถเพิ่มตัวเลือก -n 10000 ที่หางเพื่อให้ 10,000 เส้นสุดท้ายออกมาเช่นกัน
Alfe

ความคิดอื่น: สารละลาย Fifo ของคุณสามารถยืดออกได้โดยการส่งผ่านผลลัพธ์ tail -f ผ่านทาง Fifo และ Grepping mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe

@Alfe: ฉันอาจจะผิด แต่ฉันเชื่อว่ามี tail -f log การเขียนถึง FIFO จะทำให้บางระบบ (เช่น GNU / Linux) ใช้การบล็อกแบบบล็อกแทนการบัฟเฟอร์แบบบรรทัด grep อาจไม่เห็นบรรทัดที่ตรงกันเมื่อปรากฏในบันทึก ระบบอาจจัดเตรียมยูทิลิตีเพื่อเปลี่ยนบัฟเฟอร์เช่น stdbuf จาก GNU coreutils อย่างไรก็ตามยูทิลิตีดังกล่าวไม่ใช่แบบพกพา
Richard Hansen

1
@Alfe: ที่จริงแล้วดูเหมือนว่า POSIX จะไม่พูดอะไรเกี่ยวกับการบัฟเฟอร์ยกเว้นเมื่อมีปฏิสัมพันธ์กับเทอร์มินัลดังนั้นจากมุมมองมาตรฐานฉันคิดว่าโซลูชันที่เรียบง่ายของคุณนั้นดีพอ ๆ อย่างไรก็ตามฉันไม่แน่ใจ 100% เกี่ยวกับการใช้งานที่หลากหลายในแต่ละกรณีอย่างไรก็ตาม
Richard Hansen

ที่จริงตอนนี้ฉันไปเพื่อให้ง่ายยิ่งขึ้น grep -q -m 1 trigger <(tail -f log) เสนอที่อื่นและอยู่กับความจริงที่ว่า tail รันหนึ่งบรรทัดในพื้นหลังที่ยาวเกินกว่าที่ต้องการ
Alfe

13

หากคุณใช้ Bash (อย่างน้อยก็ดูเหมือนว่ามันจะไม่ได้กำหนดโดย POSIX ดังนั้นมันอาจจะหายไปในเชลล์บางตัว) คุณสามารถใช้ไวยากรณ์

grep -m 1 "Server Started" <(tail -f logfile.log)

มันใช้งานได้ดีเหมือนโซลูชั่น FIFO ที่ได้กล่าวไปแล้ว แต่ก็ง่ายกว่าในการเขียน


1
วิธีนี้ใช้ได้ผล แต่หางยังคงทำงานอยู่จนกว่าคุณจะส่ง SIGTERM (Ctrl + C, ออกคำสั่งหรือฆ่ามัน)
mems

3
@mems บรรทัดเพิ่มเติมในไฟล์บันทึกจะทำ tail จะอ่านลองส่งมันออกมาและรับ SIGPIPE ซึ่งจะยกเลิก ดังนั้นในหลักการคุณจะถูกต้อง; tail อาจทำงานไม่สิ้นสุดหากไม่มีสิ่งใดถูกเขียนไปยังล็อกไฟล์อีกต่อไป ในทางปฏิบัตินี่อาจเป็นทางออกที่ดีสำหรับคนจำนวนมาก
Alfe

8

ให้ฉันขยายคำตอบที่ @ 00prometheus (อันไหนดีที่สุด)

บางทีคุณควรใช้การหมดเวลาแทนที่จะรออย่างไม่มีกำหนด

ฟังก์ชั่นทุบตีด้านล่างจะบล็อกจนกว่าข้อความค้นหาที่กำหนดจะปรากฏขึ้นหรือถึงระยะหมดเวลา

สถานะการออกจะเป็น 0 หากพบสตริงภายในระยะเวลา

wait_str() {
  local file="$1"; shift
  local search_term="$1"; shift
  local wait_time="${1:-5m}"; shift # 5 minutes as default timeout

  (timeout $wait_time tail -F -n0 "$file" &) | grep -q "$search_term" && return 0

  echo "Timeout of $wait_time reached. Unable to find '$search_term' in '$file'"
  return 1
}

บางทีล็อกไฟล์ยังไม่มีอยู่หลังจากเปิดตัวเซิร์ฟเวอร์ของคุณ ในกรณีนั้นคุณควรรอให้ปรากฏก่อนค้นหาสตริง:

wait_server() {
  echo "Waiting for server..."
  local server_log="$1"; shift
  local wait_time="$1"; shift

  wait_file "$server_log" 10 || { echo "Server log file missing: '$server_log'"; return 1; }

  wait_str "$server_log" "Server Started" "$wait_time"
}

wait_file() {
  local file="$1"; shift
  local wait_seconds="${1:-10}"; shift # 10 seconds as default timeout

  until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do sleep 1; done

  ((++wait_seconds))
}

นี่คือวิธีการใช้งาน:

wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"

ดังนั้นอยู่ที่ไหน timeout คำสั่ง?
ayanamist

จริง ๆ แล้วใช้ timeout เป็นวิธีเดียวที่เชื่อถือได้ในการไม่หยุดรอเซิร์ฟเวอร์ที่ไม่สามารถเริ่มและออกจากระบบได้อย่างไม่มีกำหนด
gluk47

1
คำตอบนี้ดีที่สุด เพียงคัดลอกฟังก์ชั่นและเรียกมันง่ายและนำมาใช้ใหม่
Hristo Vrigazov

6

ดังนั้นหลังจากทำการทดสอบฉันพบวิธีที่รวดเร็ว 1 บรรทัดในการทำงานนี้ ดูเหมือนว่า tail-f จะหยุดทำงานเมื่อ grep หยุดทำงาน แต่มีการจับ ดูเหมือนว่าจะถูกเรียกใช้เฉพาะเมื่อไฟล์ถูกเปิดและปิด ฉันทำสิ่งนี้ได้สำเร็จโดยการต่อท้ายสตริงว่างลงในไฟล์เมื่อ grep พบการแข่งขัน

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> logfile \;

ฉันไม่แน่ใจว่าทำไมการเปิด / ปิดของไฟล์เรียกหางเพื่อรับรู้ว่าไปป์ปิดดังนั้นฉันจะไม่พึ่งพาพฤติกรรมนี้ แต่ตอนนี้ดูเหมือนว่าจะทำงาน

เหตุผลที่ปิดลงให้ดูที่แฟล็ก -F เทียบกับแฟล็ก -f


1
สิ่งนี้ทำงานได้เนื่องจากการผนวกเข้ากับสาเหตุไฟล์บันทึก tail เพื่อเอาท์พุทสายอื่น แต่จากนั้น grep ออกแล้ว (อาจเป็น - มีสภาพการแข่งขันอยู่ที่นั่น) ถ้า grep ออกจากเวลาแล้ว tail เขียนอีกบรรทัด tail จะได้รับ SIGPIPE. นั่นเป็นสาเหตุ tail เพื่อออกทันที
Richard Hansen

1
ข้อเสียของวิธีการนี้: (1) มีสภาพการแข่งขัน (อาจไม่ออกทันที) (2) ต้องมีการเข้าถึงการเขียนไฟล์บันทึก (3) คุณต้องตกลงด้วยการแก้ไขไฟล์บันทึก (4) คุณอาจเสียหาย ไฟล์บันทึก (5) ใช้งานได้เท่านั้น tail (6) คุณไม่สามารถปรับแต่งให้แตกต่างกันได้อย่างง่ายดายโดยขึ้นอยู่กับการจับคู่สตริง differnt ("เซิร์ฟเวอร์เริ่มต้น" กับ "เซิร์ฟเวอร์เริ่มต้นล้มเหลว") เพราะคุณไม่สามารถรับรหัสส่งคืนของสเตจกลางของท่อได้ มีวิธีการอื่นที่หลีกเลี่ยงปัญหาเหล่านี้ - ดูคำตอบของฉัน
Richard Hansen

6

ปัจจุบันตามที่กำหนดทั้งหมดของ tail -f วิธีแก้ไขที่นี่จะเสี่ยงต่อการรับสาย "Server Started" ที่บันทึกไว้ก่อนหน้านี้ (ซึ่งอาจมีหรือไม่มีปัญหาในกรณีเฉพาะของคุณ

แทนที่จะทำสิ่งที่ซับซ้อนเกินไปเพียงใช้อย่างชาญฉลาด tail, เช่น bmike แสดงด้วย snippit Perl ทางออกที่ง่ายที่สุดคือสิ่งนี้ retail ซึ่งได้รวมการสนับสนุน regex ด้วย เริ่มต้น และ หยุด รูปแบบเงื่อนไข:

retail -f -u "Server Started" server.log > /dev/null

ไฟล์นี้จะตามมาเหมือนปกติ tail -f จนกระทั่งแรก ใหม่ อินสแตนซ์ของสตริงนั้นปรากฏขึ้นแล้วออก (ใน -u ตัวเลือกไม่ก่อให้เกิดบรรทัดที่มีอยู่ใน 10 บรรทัดสุดท้ายของไฟล์เมื่ออยู่ในโหมด "ติดตาม" ปกติ)


ถ้าคุณใช้ GNU tail (จาก coreutils ) ตัวเลือกที่ง่ายที่สุดถัดไปคือการใช้ --pid และ FIFO (ชื่อไปป์):

mkfifo ${FIFO:=serverlog.fifo.$$}
grep -q -m 1 "Server Started" ${FIFO}  &
tail -n 0 -f server.log  --pid $! >> ${FIFO}
rm ${FIFO}

FIFO ถูกใช้เนื่องจากกระบวนการจะต้องเริ่มต้นแยกจากกันเพื่อรับและส่ง PID FIFO ยังคงประสบปัญหาเดียวกันกับที่แขวนอยู่รอบ ๆ เพื่อให้เขียนได้ทันเวลา tail ที่จะได้รับ SIGPIPE , ใช้ --pid ตัวเลือกเพื่อให้ tail ออกเมื่อมันสังเกตเห็นว่า grep ได้ยกเลิก (ใช้โดยทั่วไปในการตรวจสอบ นักเขียน กระบวนการมากกว่า ผู้อ่าน แต่ tail ไม่สนใจจริงๆ) ตัวเลือก -n 0 ใช้กับ tail เพื่อให้สายเก่าไม่เรียกการแข่งขัน


ในที่สุดคุณสามารถใช้ หาง stateful สิ่งนี้จะเก็บไฟล์ออฟเซ็ตปัจจุบันดังนั้นการเรียกใช้ในภายหลังจะแสดงเฉพาะบรรทัดใหม่ ตัวอย่างนี้ใช้ FWTK เก่า retail *:

retail "${LOGFILE:=server.log}" > /dev/null   # skip over current content
while true; do
    [ "${LOGFILE}" -nt ".${LOGFILE}.off" ] && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
    sleep 2
done

* หมายเหตุชื่อเดียวกันโปรแกรมอื่นไปยังตัวเลือกก่อนหน้า

แทนที่จะใช้ CPU-hogging loop เปรียบเทียบเวลาประทับของไฟล์กับไฟล์สถานะ ( .${LOGFILE}.off ) และนอนหลับ ใช้ " -T "เพื่อระบุตำแหน่งของไฟล์สถานะหากจำเป็นข้างต้นจะถือว่าไดเรกทอรีปัจจุบันรู้สึกฟรีที่จะข้ามเงื่อนไขนั้นหรือบน Linux คุณสามารถใช้งานได้อย่างมีประสิทธิภาพยิ่งขึ้น inotifywait แทน:

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done

ฉันสามารถรวม retail ด้วยการหมดเวลาเช่น "ถ้า 120 วินาทีผ่านไปและการขายปลีกยังไม่ได้อ่านบรรทัดให้รหัสข้อผิดพลาดและออกจากการค้าปลีก"?
kiltek

@kiltek ใช้ GNU timeout (coreutils) เพื่อเปิดตัว retail และเพียงตรวจสอบรหัสทางออก 124 เมื่อหมดเวลา ( timeout จะฆ่าคำสั่งใด ๆ ที่คุณใช้เพื่อเริ่มต้นหลังจากเวลาที่คุณตั้งไว้)
mr.spuratic

4

นี่จะค่อนข้างยุ่งยากเพราะคุณจะต้องเข้าสู่กระบวนการควบคุมและส่งสัญญาณ kludge เพิ่มเติมจะเป็นโซลูชันสคริปต์ที่สองโดยใช้การติดตาม PID ดีกว่าจะใช้ชื่อ pipes อย่างนี้.

คุณใช้เชลล์สคริปต์อะไร

สำหรับวิธีแก้ปัญหาสคริปต์ที่รวดเร็วและสกปรกฉันจะสร้างสคริปต์ Perl โดยใช้ ไฟล์: หาง

use File::Tail;
$file=File::Tail->new(name=>$name, maxinterval=>300, adjustafter=>7);
while (defined($line=$file->read)) {
    last if $line =~ /Server started/;
}

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

สิ่งเหล่านี้ควรเกี่ยวข้องกับการเรียนรู้เพียงเล็กน้อยในการปรับใช้โฟลว์คอนโทรลที่คุณกำลังมองหา


ใช้ทุบตี perl-fu ของฉันไม่แข็งแรง แต่ฉันจะให้ช็อตนี้
Alex Hofsteede

ใช้ท่อ - พวกเขาชอบทุบตีและทุบตีรักพวกเขา (และซอฟต์แวร์สำรองข้อมูลของคุณจะเคารพคุณเมื่อโดนหนึ่งในท่อของคุณ)
bmike

maxinterval=>300 หมายความว่ามันจะตรวจสอบไฟล์ทุกห้านาที เนื่องจากฉันรู้ว่าบรรทัดของฉันจะปรากฏในไฟล์ในไม่ช้าฉันจึงใช้การหยั่งเสียงเชิงรุกมากขึ้น: maxinterval=>0.2, adjustafter=>10000
Stephen Ostermiller

2

รอให้ไฟล์ปรากฏ

while [ ! -f /path/to/the.file ] 
do sleep 2; done

รอให้สตริงปรากฏในไฟล์

while ! grep "the line you're searching for" /path/to/the.file  
do sleep 10; done

https://superuser.com/a/743693/129669


2
การสำรวจนี้มีข้อบกพร่องหลักสองประการ: 1. เสียเวลาในการคำนวณโดยไปที่บันทึกอีกครั้งและอีกครั้ง พิจารณา /path/to/the.file ซึ่งมีขนาดใหญ่ 1.4GB จากนั้นเป็นที่ชัดเจนว่านี่เป็นปัญหา 2. รอนานเกินความจำเป็นเมื่อรายการบันทึกปรากฏในกรณีที่เลวร้ายที่สุด 10 วินาที
Alfe

2

ฉันไม่สามารถจินตนาการโซลูชันที่สะอาดกว่านี้ได้:

#!/usr/bin/env bash
# file : untail.sh
# usage: untail.sh logfile.log "Server Started"
(echo $BASHPID; tail -f $1) | while read LINE ; do
    if [ -z $TPID ]; then
        TPID=$LINE # the first line is used to store the previous subshell PID
    else
        echo "$LINE"; [[ "$LINE" == *"${*:2}"* ]] && kill -3 $TPID && break
    fi
done

ตกลงบางทีชื่ออาจมีการปรับปรุง ...

ข้อดี:

  • ไม่ใช้สาธารณูปโภคพิเศษใด ๆ
  • มันไม่ได้เขียนลงดิสก์
  • มันออกจากหางอย่างงดงามและปิดท่อ
  • มันค่อนข้างสั้นและเข้าใจง่าย

2

คุณไม่จำเป็นต้องมีหางทำเช่นนั้น ฉันคิดว่า ดู คำสั่งคือสิ่งที่คุณกำลังมองหา คำสั่ง watch มอนิเตอร์การส่งออกของไฟล์และสามารถยกเลิกด้วย -G ตัวเลือกเมื่อมีการเปลี่ยนแปลงการส่งออก

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction

1
เนื่องจากสิ่งนี้ทำงานทุกๆสองวินาทีจึงไม่ออกทันทีเมื่อบรรทัดปรากฏในไฟล์บันทึก นอกจากนี้ยังใช้งานไม่ได้หากไฟล์บันทึกมีขนาดใหญ่มาก
Richard Hansen


1

อเล็กซ์ฉันคิดว่าอันนี้จะช่วยคุณได้มาก

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> /dev/null ;

คำสั่งนี้จะไม่ให้รายการใน logfile แต่จะ grep เงียบ ๆ ...


1
สิ่งนี้ใช้ไม่ได้คุณต้องต่อท้าย logfile มิฉะนั้นอาจเป็นเวลานานโดยพลการมาก่อน tail ส่งออกสายอื่นและตรวจพบว่า grep ได้ตายไปแล้ว (ผ่าน SIGPIPE )
Richard Hansen

1

นี่เป็นวิธีแก้ปัญหาที่ดีกว่ามากซึ่งคุณไม่จำเป็นต้องเขียนลงใน logfile ซึ่งเป็นอันตรายมาก

sh -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

ขณะนี้มันมีผลข้างเคียงเพียงอันเดียว, tail กระบวนการจะยังคงอยู่ในพื้นหลังจนกว่าบรรทัดถัดไปจะถูกเขียนลงในบันทึก


tail -n +0 -f เริ่มต้นจากจุดเริ่มต้นของไฟล์ tail -n 0 -f เริ่มจากจุดสิ้นสุดของไฟล์
Stephen Ostermiller

1
ผลข้างเคียงอื่นที่ฉันได้รับ: myscript.sh: line 14: 7845 Terminated sh -c 'tail...
Stephen Ostermiller

ฉันเชื่อว่า "รายการถัดไป" ควรเป็น "บรรทัดถัดไป" ในคำตอบนี้
Stephen Ostermiller

1

โซลูชันอื่น ๆ ที่นี่มีปัญหาหลายประการ:

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

นี่คือสิ่งที่ฉันเกิดขึ้นกับการใช้ Tomcat เป็นตัวอย่าง (ลบแฮชถ้าคุณต้องการที่จะเห็นบันทึกในขณะที่มันเริ่มต้น):

function startTomcat {
    loggingProcessStartCommand="${CATALINA_HOME}/bin/startup.sh"
    loggingProcessOwner="root"
    loggingProcessCommandLinePattern="${JAVA_HOME}"
    logSearchString="org.apache.catalina.startup.Catalina.start Server startup"
    logFile="${CATALINA_BASE}/log/catalina.out"

    lineNumber="$(( $(wc -l "${logFile}" | awk '{print $1}') + 1 ))"
    ${loggingProcessStartCommand}
    while [[ -z "$(sed -n "${lineNumber}p" "${logFile}" | grep "${logSearchString}")" ]]; do
        [[ -z "$(ps -ef | grep "^${loggingProcessOwner} .* ${loggingProcessCommandLinePattern}" | grep -v grep)" ]] && { echo "[ERROR] Tomcat failed to start"; return 1; }
        [[ $(wc -l "${logFile}" | awk '{print $1}') -lt ${lineNumber} ]] && continue
        #sed -n "${lineNumber}p" "${logFile}"
        let lineNumber++
    done
    #sed -n "${lineNumber}p" "${logFile}"
    echo "[INFO] Tomcat has started"
}

1

tail คำสั่งสามารถพื้นหลังและ pid ของมันสะท้อนไป grep subshell ใน grep แบ่งใช้ตัวจัดการกับดักบน EXIT สามารถฆ่า tail คำสั่ง

( (sleep 1; exec tail -f logfile.log) & echo $! ; wait ) | 
     (trap 'kill "$pid"' EXIT; pid="$(head -1)"; grep -m 1 "Server Started")

1

อ่านทั้งหมด tldr: แยกการสิ้นสุดของหางออกจาก grep

ทั้งสองรูปแบบที่สะดวกที่สุดคือ

( tail -f logfile.log & ) | grep -q "Server Started"

และถ้าคุณมีทุบตี

grep -m 1 "Server Started" <(tail -f logfile.log)

แต่ถ้าหางนั้นนั่งอยู่ด้านหลังรบกวนคุณก็จะมีวิธีที่ดีกว่าฟีดหรือคำตอบอื่น ต้องทุบตี

coproc grep -m 1 "Server Started"
tail -F /tmp/x --pid $COPROC_PID >&${COPROC[1]}

หรือถ้ามันไม่ได้เป็นหางซึ่งเป็นสิ่งที่ส่งออก

coproc command that outputs
grep -m 1 "Sever Started" ${COPROC[0]}
kill $COPROC_PID

0

ลองใช้ inotify (inotifywait)

คุณตั้งค่า inotifywait สำหรับการเปลี่ยนแปลงไฟล์ใด ๆ จากนั้นตรวจสอบไฟล์ด้วย grep หากไม่พบให้ทำการรัน inotifywait อีกครั้งหากพบว่าออกจากลูป ... Smth เช่นนั้น


ด้วยวิธีนี้ ทั้งหมด ไฟล์จะต้องถูกตรวจสอบซ้ำทุกครั้งที่มีการเขียน ใช้งานไม่ได้กับไฟล์บันทึก
grawity

1
อีกวิธีหนึ่งคือการสร้างสองสคริปต์: 1. tail -f logfile.log | grep -m 1 "เซิร์ฟเวอร์เริ่มต้น" & gt; / tmp / พบ 2. firstscript.sh & amp; MYPID = $!; inotifywait -e MODIFY / tmp / found; kill -KILL - $ MYPID
Evengard

ฉันรักคุณที่จะแก้ไขคำตอบของคุณเพื่อแสดงการจับ PID จากนั้นใช้ inotifywait - โซลูชันที่สง่างามที่จะเข้าใจง่ายสำหรับคนที่เคย grep แต่ต้องการเครื่องมือที่ซับซ้อนกว่า
bmike

PID ของสิ่งที่คุณต้องการจับ? ฉันสามารถลองทำมันได้ถ้าคุณอธิบายสิ่งที่คุณต้องการเพิ่มอีกนิด
Evengard

0

คุณต้องการออกทันทีที่มีการเขียนบรรทัด แต่คุณต้องการออกหลังจากหมดเวลาด้วย:

if (timeout 15s tail -F -n0 "stdout.log" &) | grep -q "The string that says the startup is successful" ; then
    echo "Application started with success."
else
    echo "Startup failed."
    tail stderr.log stdout.log
    exit 1
fi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.