Ada beberapa cara tail
untuk keluar:
Pendekatan yang Buruk: Memaksa tail
untuk menulis baris lain
Anda dapat memaksa tail
untuk menulis baris output lain segera setelah grep
menemukan kecocokan dan keluar. Ini akan menyebabkan tail
untuk mendapatkan SIGPIPE
, menyebabkannya keluar. Salah satu cara untuk melakukan ini adalah memodifikasi file yang dipantau tail
setelah grep
keluar.
Berikut ini beberapa contoh kode:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Dalam contoh ini, cat
tidak akan keluar sampai grep
stdout ditutup, jadi tail
tidak mungkin untuk dapat menulis ke pipa sebelum grep
memiliki kesempatan untuk menutup stdinnya. cat
digunakan untuk menyebarkan output standar yang grep
tidak dimodifikasi.
Pendekatan ini relatif sederhana, tetapi ada beberapa kelemahan:
- Jika
grep
menutup stdout sebelum menutup stdin, akan selalu ada kondisi balapan: grep
menutup stdout, memicu cat
untuk keluar, memicu echo
, memicu tail
untuk menghasilkan garis. Jika baris ini dikirim ke grep
sebelum grep
memiliki kesempatan untuk menutup stdin, tail
tidak akan mendapatkan SIGPIPE
sampai menulis baris lain.
- Ini membutuhkan akses tulis ke file log.
- Anda harus OK dengan memodifikasi file log.
- Anda dapat merusak file log jika Anda kebetulan menulis pada saat yang sama dengan proses lain (penulisan mungkin disisipkan, menyebabkan baris baru muncul di tengah pesan log).
- Pendekatan ini khusus untuk
tail
— itu tidak akan bekerja dengan program lain.
- Tahap pipa ketiga menyulitkan untuk mendapatkan akses ke kode pengembalian tahap pipa kedua (kecuali jika Anda menggunakan ekstensi POSIX seperti array
bash
's PIPESTATUS
). Ini bukan masalah besar dalam kasus ini karena grep
akan selalu mengembalikan 0, tetapi secara umum tahap tengah mungkin diganti dengan perintah berbeda yang kode pengembaliannya Anda pedulikan (misalnya, sesuatu yang mengembalikan 0 ketika "server mulai" terdeteksi, 1 ketika "server gagal memulai" terdeteksi).
Pendekatan selanjutnya menghindari keterbatasan ini.
Pendekatan yang Lebih Baik: Hindari Saluran Pipa
Anda dapat menggunakan FIFO untuk menghindari pipa sama sekali, yang memungkinkan eksekusi berlanjut setelah grep
kembali. Sebagai contoh:
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}"
Garis yang ditandai dengan komentar # optional
dapat dihapus dan program akan tetap bekerja; tail
hanya akan berlama-lama sampai membaca baris input lain atau terbunuh oleh proses lain.
Keuntungan dari pendekatan ini adalah:
- Anda tidak perlu memodifikasi file log
- selain itu pendekatan ini berfungsi untuk utilitas lain
tail
- itu tidak menderita dari kondisi balapan
- Anda dapat dengan mudah mendapatkan nilai kembali
grep
(atau perintah alternatif apa pun yang Anda gunakan)
Kelemahan dari pendekatan ini adalah kompleksitas, terutama mengelola FIFO: Anda harus secara aman menghasilkan nama file sementara, dan Anda harus memastikan bahwa FIFO sementara dihapus bahkan jika pengguna menekan Ctrl-C di tengah-tengah naskah. Ini bisa dilakukan menggunakan perangkap.
Pendekatan Alternatif: Kirim Pesan ke Kill tail
Anda bisa mendapatkan tail
tahap pipa untuk keluar dengan mengirimkannya seperti sinyal SIGTERM
. Tantangannya adalah mengetahui dua hal di tempat yang sama dalam kode: tail
PID dan apakah grep
telah keluar.
Dengan pipeline like tail -f ... | grep ...
, mudah untuk memodifikasi tahap pipeline pertama untuk menyimpan tail
PID dalam sebuah variabel dengan latar belakang tail
dan membaca $!
. Juga mudah untuk memodifikasi tahap pipa kedua untuk dijalankan kill
ketika grep
keluar. Masalahnya adalah bahwa dua tahap pipa berjalan dalam "lingkungan eksekusi" yang terpisah (dalam terminologi standar POSIX) sehingga tahap pipa kedua tidak dapat membaca variabel yang ditetapkan oleh tahap pipa pertama. Tanpa menggunakan variabel shell, entah itu tahap kedua harus entah bagaimana mencari tahu tail
PID sehingga bisa membunuh tail
ketika grep
kembali, atau tahap pertama harus entah bagaimana diberitahu ketika grep
kembali.
Tahap kedua bisa digunakan pgrep
untuk mendapatkan tail
PID, tetapi itu tidak bisa diandalkan (Anda mungkin cocok dengan proses yang salah) dan non-portabel ( pgrep
tidak ditentukan oleh standar POSIX).
Tahap pertama bisa mengirim PID ke tahap kedua melalui pipa dengan echo
memasukkan PID, tetapi string ini akan dicampur dengan tail
output. Demultiplexing keduanya mungkin memerlukan skema pelarian yang kompleks, tergantung pada output dari tail
.
Anda dapat menggunakan FIFO agar tahap pipa kedua memberi tahu tahap pipa pertama saat grep
keluar. Maka tahap pertama bisa membunuh tail
. Berikut ini beberapa contoh kode:
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}"
Pendekatan ini memiliki semua pro dan kontra dari pendekatan sebelumnya, kecuali itu lebih rumit.
Peringatan Tentang Buffering
POSIX memungkinkan stdin dan stdout stream sepenuhnya buffered, yang berarti bahwa tail
output mungkin tidak diproses oleh grep
untuk waktu yang lama secara sewenang-wenang. Seharusnya tidak ada masalah pada sistem GNU: grep
Penggunaan GNU read()
, yang menghindari semua buffering, dan GNU tail -f
melakukan panggilan rutin fflush()
ketika menulis ke stdout. Sistem non-GNU mungkin harus melakukan sesuatu yang khusus untuk menonaktifkan atau membersihkan buffer secara berkala.