Có một vài cách tailđể thoát ra:
Cách tiếp cận kém: Buộc tailphải viết một dòng khác
Bạn có thể buộc tailphải viết một dòng đầu ra khác ngay sau khi grepđã tìm thấy một kết quả khớp và thoát. Điều này sẽ gây ra tailđể có được một SIGPIPE, khiến nó thoát ra. Một cách để làm điều này là sửa đổi tệp đang được theo dõi tailsau khi grepthoát.
Dưới đây là một số mã ví dụ:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Trong ví dụ này, catsẽ không thoát cho đến khi grepđóng thiết bị xuất chuẩn, do đó tailkhông có khả năng ghi vào đường ống trước khi grepcó cơ hội đóng stdin của nó. catđược sử dụng để tuyên truyền đầu ra tiêu chuẩn của grepkhông thay đổi.
Cách tiếp cận này tương đối đơn giản, nhưng có một số nhược điểm:
- Nếu
grepđóng stdout trước khi đóng stdin, sẽ luôn có một điều kiện cuộc đua: grepđóng stdout, kích hoạt catđể thoát, kích hoạt echo, kích hoạt tailđể tạo ra một dòng. Nếu dòng này được gửi đến greptrước đó grepđã có cơ hội đóng stdin, tailsẽ không nhận được SIGPIPEcho đến khi nó viết một dòng khác.
- Nó yêu cầu truy cập ghi vào tệp nhật ký.
- Bạn phải ổn với việc sửa đổi tệp nhật ký.
- Bạn có thể làm hỏng tệp nhật ký nếu bạn tình cờ ghi cùng lúc với một quá trình khác (việc ghi có thể bị xen kẽ, khiến một dòng mới xuất hiện ở giữa thông điệp tường trình).
- Cách tiếp cận này là cụ thể đối với tinh thần
tailsẽ không làm việc với các chương trình khác.
- Giai đoạn đường ống thứ ba làm cho nó khó khăn để có được quyền truy cập vào mã trở lại của giai đoạn đường ống thứ hai (trừ khi bạn đang sử dụng một phần mở rộng POSIX như
bash's PIPESTATUSmảng). Đây không phải là vấn đề lớn trong trường hợp này vì grepsẽ luôn trả về 0, nhưng nói chung, giai đoạn giữa có thể được thay thế bằng một lệnh khác có mã trả về mà bạn quan tâm (ví dụ: một cái gì đó trả về 0 khi phát hiện "máy chủ khởi động", 1 khi "máy chủ không khởi động được" được phát hiện).
Các phương pháp tiếp theo tránh những hạn chế này.
Cách tiếp cận tốt hơn: Tránh đường ống
Bạn có thể sử dụng một FIFO để tránh đường ống hoàn toàn, cho phép thực hiện tiếp tục một lần greptrở lại. Ví dụ:
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}"
Các dòng được đánh dấu bằng nhận xét # optionalcó thể được gỡ bỏ và chương trình sẽ vẫn hoạt động; tailsẽ chỉ nán lại cho đến khi nó đọc một dòng đầu vào khác hoặc bị giết bởi một quá trình khác.
Ưu điểm của phương pháp này là:
- bạn không cần phải sửa đổi tệp nhật ký
- Cách tiếp cận hoạt động cho các tiện ích khác bên cạnh
tail
- nó không bị tình trạng chủng tộc
- bạn có thể dễ dàng nhận được giá trị trả về của
grep(hoặc bất kỳ lệnh thay thế nào bạn đang sử dụng)
Nhược điểm của phương pháp này là phức tạp, đặc biệt là quản lý FIFO: Bạn sẽ cần tạo một tên tệp tạm thời một cách an toàn và bạn sẽ cần đảm bảo rằng FIFO tạm thời bị xóa ngay cả khi người dùng nhấn Ctrl-C ở giữa kịch bản. Điều này có thể được thực hiện bằng cách sử dụng một cái bẫy.
Phương pháp thay thế: Gửi tin nhắn để giết tail
Bạn có thể lấy tailgiai đoạn đường ống để thoát bằng cách gửi tín hiệu như thế SIGTERM. Thách thức là đáng tin cậy khi biết hai điều ở cùng một vị trí trong mã: tailPID và liệu grepđã thoát.
Với một đường ống như thế tail -f ... | grep ..., thật dễ dàng để sửa đổi giai đoạn đường ống đầu tiên để lưu tailPID của một biến bằng cách đặt nền tailvà đọc $!. Cũng dễ dàng sửa đổi giai đoạn đường ống thứ hai để chạy killkhi grepthoát. Vấn đề là hai giai đoạn của đường ống chạy trong "môi trường thực thi" riêng biệt (theo thuật ngữ của tiêu chuẩn POSIX) nên giai đoạn đường ống thứ hai không thể đọc bất kỳ biến nào được đặt bởi giai đoạn đường ống đầu tiên. Không sử dụng các biến shell, giai đoạn thứ hai bằng cách nào đó phải tìm ra tailPID của nó để nó có thể tắt tailkhi greptrả về hoặc giai đoạn đầu tiên phải được thông báo bằng cách nào đó khi greptrả về.
Giai đoạn thứ hai có thể sử dụng pgrepđể có được tailPID, nhưng điều đó sẽ không đáng tin cậy (bạn có thể khớp quy trình sai) và không di động ( pgrepkhông được quy định bởi tiêu chuẩn POSIX).
Giai đoạn đầu tiên có thể gửi PID đến giai đoạn thứ hai thông qua đường ống bằng cách nhập echovào PID, nhưng chuỗi này sẽ bị lẫn với tailđầu ra của. Việc khử nhiễu cả hai có thể yêu cầu một sơ đồ thoát phức tạp, tùy thuộc vào đầu ra của tail.
Bạn có thể sử dụng một FIFO để có giai đoạn đường ống thứ hai thông báo cho giai đoạn đường ống đầu tiên khi grepthoát. Sau đó, giai đoạn đầu tiên có thể giết chết tail. Dưới đây là một số mã ví dụ:
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}"
Cách tiếp cận này có tất cả ưu và nhược điểm của cách tiếp cận trước, ngoại trừ nó phức tạp hơn.
Một cảnh báo về bộ đệm
POSIX cho phép các luồng stdin và stdout được đệm hoàn toàn, điều đó có nghĩa taillà đầu ra của nó có thể không được xử lý greptrong một thời gian dài tùy ý. Không nên có bất kỳ vấn đề nào trên các hệ thống GNU: GNU grepsử dụng read(), giúp tránh tất cả các bộ đệm và GNU tail -fthực hiện các cuộc gọi thông thường fflush()khi ghi vào thiết bị xuất chuẩn. Các hệ thống không phải GNU có thể phải làm một cái gì đó đặc biệt để vô hiệu hóa hoặc thường xuyên xóa bộ đệm.