Ada beberapa cara tailuntuk keluar:
Pendekatan yang Buruk: Memaksa tailuntuk menulis baris lain
Anda dapat memaksa tailuntuk menulis baris output lain segera setelah grepmenemukan kecocokan dan keluar. Ini akan menyebabkan tailuntuk mendapatkan SIGPIPE, menyebabkannya keluar. Salah satu cara untuk melakukan ini adalah memodifikasi file yang dipantau tailsetelah grepkeluar.
Berikut ini beberapa contoh kode:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Dalam contoh ini, cattidak akan keluar sampai grepstdout ditutup, jadi tailtidak mungkin untuk dapat menulis ke pipa sebelum grepmemiliki kesempatan untuk menutup stdinnya. catdigunakan untuk menyebarkan output standar yang greptidak dimodifikasi.
Pendekatan ini relatif sederhana, tetapi ada beberapa kelemahan:
- Jika
grepmenutup stdout sebelum menutup stdin, akan selalu ada kondisi balapan: grepmenutup stdout, memicu catuntuk keluar, memicu echo, memicu tailuntuk menghasilkan garis. Jika baris ini dikirim ke grepsebelum grepmemiliki kesempatan untuk menutup stdin, tailtidak akan mendapatkan SIGPIPEsampai 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 grepakan 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 grepkembali. 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 # optionaldapat dihapus dan program akan tetap bekerja; tailhanya 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 tailtahap pipa untuk keluar dengan mengirimkannya seperti sinyal SIGTERM. Tantangannya adalah mengetahui dua hal di tempat yang sama dalam kode: tailPID dan apakah greptelah keluar.
Dengan pipeline like tail -f ... | grep ..., mudah untuk memodifikasi tahap pipeline pertama untuk menyimpan tailPID dalam sebuah variabel dengan latar belakang taildan membaca $!. Juga mudah untuk memodifikasi tahap pipa kedua untuk dijalankan killketika grepkeluar. 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 tailPID sehingga bisa membunuh tailketika grepkembali, atau tahap pertama harus entah bagaimana diberitahu ketika grepkembali.
Tahap kedua bisa digunakan pgrepuntuk mendapatkan tailPID, tetapi itu tidak bisa diandalkan (Anda mungkin cocok dengan proses yang salah) dan non-portabel ( pgreptidak ditentukan oleh standar POSIX).
Tahap pertama bisa mengirim PID ke tahap kedua melalui pipa dengan echomemasukkan PID, tetapi string ini akan dicampur dengan tailoutput. 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 grepkeluar. 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 tailoutput mungkin tidak diproses oleh grepuntuk waktu yang lama secara sewenang-wenang. Seharusnya tidak ada masalah pada sistem GNU: grepPenggunaan GNU read(), yang menghindari semua buffering, dan GNU tail -fmelakukan panggilan rutin fflush()ketika menulis ke stdout. Sistem non-GNU mungkin harus melakukan sesuatu yang khusus untuk menonaktifkan atau membersihkan buffer secara berkala.