Bagaimana cara menangkap STDOUT / STDERR yang dipesan dan menambahkan cap waktu / awalan?

25

Saya telah menjelajahi hampir semua pertanyaan serupa yang tersedia , tetapi tidak berhasil.

Izinkan saya menjelaskan masalah secara rinci:

Saya menjalankan beberapa skrip yang tidak dijaga dan ini dapat menghasilkan output standar dan garis kesalahan standar, saya ingin menangkap mereka dalam urutan yang tepat seperti yang ditampilkan oleh terminal emulator dan kemudian menambahkan awalan seperti "STDERR:" dan "STDOUT:" kepada mereka.

Saya telah mencoba menggunakan pipa dan bahkan pendekatan berbasis epoll pada mereka, tetapi tidak berhasil. Saya pikir solusi dalam penggunaan pty, meskipun saya tidak menguasai hal itu. Saya juga telah mengintip kode sumber VTE Gnome , tetapi itu belum banyak produktif.

Idealnya saya akan menggunakan Go daripada Bash untuk mencapai ini, tetapi saya belum bisa. Sepertinya pipa otomatis melarang menjaga urutan garis yang benar karena buffering.

Adakah yang bisa melakukan hal serupa? Atau itu tidak mungkin? Saya pikir jika terminal emulator dapat melakukannya, maka itu tidak - mungkin dengan membuat program C kecil yang menangani PTY (s) berbeda?

Idealnya saya akan menggunakan input asinkron untuk membaca 2 aliran ini (STDOUT dan STDERR) dan kemudian mencetak ulang kedua kebutuhan saya, tetapi urutan input sangat penting!

CATATAN: Saya mengetahui stderred tetapi tidak berfungsi untuk saya dengan skrip Bash dan tidak dapat dengan mudah diedit untuk menambahkan awalan (karena pada dasarnya membungkus banyak syscall).

Pembaruan: ditambahkan di bawah dua inti

(penundaan acak sub-detik dapat ditambahkan dalam skrip sampel yang saya berikan untuk membuktikan hasil yang konsisten)

Pembaruan: solusi untuk pertanyaan ini juga akan menyelesaikan pertanyaan lain ini , seperti yang ditunjukkan @Gilles. Namun saya sampai pada kesimpulan bahwa tidak mungkin melakukan apa yang diminta di sana-sini. Ketika menggunakan 2>&1kedua aliran digabung dengan benar pada tingkat pty / pipa, tetapi untuk menggunakan aliran secara terpisah dan dalam urutan yang benar seseorang memang harus menggunakan pendekatan stderred yang melibatkan pengait syscall dan dapat dilihat sebagai kotor dalam banyak hal.

Saya akan bersemangat untuk memperbarui pertanyaan ini jika seseorang dapat menolak hal di atas.

Deim0s
sumber
1
Bukankah ini yang kamu inginkan? stackoverflow.com/questions/21564/…
slm
@slm mungkin tidak, karena kebutuhan OP prepend berbeda string untuk berbagai aliran.
peterph
Bisakah Anda berbagi mengapa pesanan begitu penting? Mungkin ada cara lain untuk mengatasi masalah Anda ...
peterph
@peterph itu prasyarat, jika saya tidak dapat memiliki output yang konsisten, saya lebih suka mengirimkannya ke / dev / null daripada membacanya dan menjadi bingung karenanya :) 2> & 1 mempertahankan pesanan misalnya, tetapi tidak mengizinkan jenis tersebut penyesuaian yang saya tanyakan dalam pertanyaan ini
Deim0s

Jawaban:

12

Anda mungkin menggunakan proses-proses. Wrapper sederhana yang mengumpankan kedua output dari perintah yang diberikan ke dua sedinstance (satu untuk stderryang lainnya stdout), yang melakukan penandaan.

#!/bin/bash
exec 3>&1
coproc SEDo ( sed "s/^/STDOUT: /" >&3 )
exec 4>&2-
coproc SEDe ( sed "s/^/STDERR: /" >&4 )
eval $@ 2>&${SEDe[1]} 1>&${SEDo[1]}
eval exec "${SEDo[1]}>&-"
eval exec "${SEDe[1]}>&-"

Perhatikan beberapa hal:

  1. Ini adalah mantra ajaib bagi banyak orang (termasuk saya) - karena suatu alasan (lihat jawaban terkait di bawah).

  2. Tidak ada jaminan itu tidak akan sesekali bertukar beberapa baris - itu semua tergantung pada penjadwalan proses-proses. Sebenarnya hampir dijamin bahwa pada suatu saat akan terjadi. Yang mengatakan, jika menjaga agar pesanan tetap sama, Anda harus memproses data dari keduanya stderrdan stdindalam proses yang sama, jika tidak, penjadwal kernel dapat (dan akan) mengacaukannya.

    Jika saya memahami masalahnya dengan benar, itu berarti Anda perlu menginstruksikan shell untuk mengalihkan kedua aliran ke satu proses (yang dapat dilakukan AFAIK). Masalahnya dimulai ketika proses itu mulai memutuskan apa yang harus ditindaklanjuti terlebih dahulu - itu harus polling kedua sumber data dan pada beberapa titik masuk ke dalam keadaan di mana ia akan memproses satu aliran dan data tiba di kedua aliran sebelum selesai. Dan di situlah ia rusak. Ini juga berarti, bahwa membungkus output syscalls seperti stderredmungkin adalah satu-satunya cara untuk mencapai hasil yang Anda inginkan (dan bahkan kemudian Anda mungkin memiliki masalah begitu sesuatu menjadi multithreaded pada sistem multiprosesor).

Sejauh coprocesses pastikan untuk membaca jawaban Stéphane yang sangat baik di Bagaimana Anda menggunakan perintah coproc di Bash? untuk wawasan yang mendalam.

peterph
sumber
Terima kasih @pheterph atas jawaban Anda, namun saya secara khusus mencari cara untuk mempertahankan pesanan. Catatan: Saya pikir penerjemah Anda harus bash karena proses penggantian yang Anda gunakan (saya dapatkan ./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpecteddengan menyalin / menempelkan skrip Anda)
Deim0s
Sangat mungkin begitu, saya menjalankannya bashdengan /bin/sh(tidak yakin mengapa saya memilikinya di sana).
peterph
Saya telah memperbarui sedikit pertanyaan, mengenai di mana aliran campuran bisa terjadi.
peterph
1
eval $@cukup buggy. Gunakan "$@"jika Anda ingin menjalankan argumen sebagai baris perintah yang tepat - menambahkan lapisan evalinterpretasi ke sekelompok sulit diprediksi (dan berpotensi berbahaya, jika Anda meneruskan nama file atau konten lain yang tidak Anda kontrol sebagai argumen) perilaku, dan gagal mengutip bahkan lebih banyak (membagi nama dengan spasi menjadi beberapa kata, memperluas gumpalan bahkan jika mereka sebelumnya dikutip secara literal, dll).
Charles Duffy
1
Selain itu, dalam bash proses-proses yang cukup modern untuk dimiliki, Anda tidak perlu eval menutup deskriptor file yang disebutkan dalam variabel. exec {SEDo[1]}>&-akan bekerja apa adanya (ya, kurangnya $sebelum {disengaja).
Charles Duffy
5

Metode # 1. Menggunakan deskriptor file dan awk

Bagaimana dengan sesuatu seperti ini menggunakan solusi dari SO T&J ini berjudul: Apakah ada utilitas Unix untuk menambahkan cap waktu ke baris teks? dan T&J SO ini berjudul: pipa STDOUT dan STDERR ke dua proses berbeda dalam skrip shell? .

Pendekatan

Langkah 1, kami membuat 2 fungsi di Bash yang akan melakukan pesan cap waktu ketika dipanggil:

$ msgOut () {  awk '{ print strftime("STDOUT: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }
$ msgErr () {  awk '{ print strftime("STDERR: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }

Langkah 2 Anda akan menggunakan fungsi di atas seperti untuk mendapatkan pesan yang diinginkan:

$ { { { ...command/script... } 2>&3; } 2>&3 | msgErr; } 3>&1 1>&2 | msgOut

Contoh

Di sini saya telah membuat contoh yang akan menulis ake STDOUT, tidur selama 10 detik, dan kemudian menulis keluaran ke STDERR. Ketika kami menempatkan urutan perintah ini ke dalam konstruk kami di atas, kami menerima pesan seperti yang Anda tentukan.

$ { { echo a; sleep 10; echo >&2 b; } 2>&3 | \
    msgErr; } 3>&1 1>&2 | msgOut
STDERR: 2014-09-26 09:22:12 a
STDOUT: 2014-09-26 09:22:22 b

Metode # 2. Menggunakan annotate-output

Ada alat yang disebut annotate-outputitu bagian dari devscriptspaket yang akan melakukan apa yang Anda inginkan. Satu-satunya batasan adalah ia harus menjalankan skrip untuk Anda.

Contoh

Jika kita menempatkan urutan perintah contoh di atas ke dalam skrip yang disebut mycmds.bashseperti:

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b

Kita kemudian dapat menjalankannya seperti ini:

$ annotate-output ./mycmds.bash 
09:48:00 I: Started ./mycmds.bash
09:48:00 O: a
09:48:10 E: b
09:48:10 I: Finished with exitcode 0

Format output dapat dikontrol untuk bagian cap waktu tetapi tidak lebih dari itu. Tapi ini mirip dengan apa yang Anda cari, jadi mungkin sesuai dengan tagihan.

slm
sumber
1
sayangnya ini juga tidak menyelesaikan masalah kemungkinan bertukar beberapa baris.
peterph
persis. Saya pikir jawaban untuk pertanyaan saya ini adalah "tidak mungkin". Acara dengan stderredAnda tidak dapat dengan mudah menentukan batas-batas garis (mencobanya akan menjadi retas). Saya ingin melihat apakah seseorang dapat membantu saya dengan masalah ini, tetapi ternyata semua orang ingin melepaskan satu kendala ( urutan ) yang menjadi dasar untuk pertanyaan
Deim0s
Langkah 1 Metode 1 membutuhkan {lain di depan untuk bekerja dengan benar.
Austin Hanson