Có một vài cách tail
để thoát ra:
Cách tiếp cận kém: Buộc tail
phải viết một dòng khác
Bạn có thể buộc tail
phả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 tail
sau khi grep
thoá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, cat
sẽ không thoát cho đến khi grep
đóng thiết bị xuất chuẩn, do đó tail
không có khả năng ghi vào đường ống trước khi grep
có 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 grep
khô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 grep
trước đó grep
đã có cơ hội đóng stdin, tail
sẽ không nhận được SIGPIPE
cho đế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
tail
sẽ 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 PIPESTATUS
mảng). Đây không phải là vấn đề lớn trong trường hợp này vì grep
sẽ 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 grep
trở 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 # optional
có thể được gỡ bỏ và chương trình sẽ vẫn hoạt động; tail
sẽ 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 tail
giai đ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ã: tail
PID 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 tail
PID của một biến bằng cách đặt nền tail
và đọc $!
. Cũng dễ dàng sửa đổi giai đoạn đường ống thứ hai để chạy kill
khi grep
thoá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 tail
PID của nó để nó có thể tắt tail
khi grep
trả về hoặc giai đoạn đầu tiên phải được thông báo bằng cách nào đó khi grep
trả về.
Giai đoạn thứ hai có thể sử dụng pgrep
để có được tail
PID, 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 ( pgrep
khô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 echo
và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 grep
thoá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 tail
là đầu ra của nó có thể không được xử lý grep
trong 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 grep
sử dụng read()
, giúp tránh tất cả các bộ đệm và GNU tail -f
thự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.