Hanya tampilkan stderr di layar tetapi tulis stdout dan stderr ke file

24

Bagaimana saya bisa menggunakan BASH magic untuk mencapai ini?
Saya hanya ingin melihat output stderr di layar,
tetapi saya ingin stdout dan stderr ditulis ke file.

Klarifikasi: Saya ingin stdout dan stderr berakhir di file yang sama. Dalam urutan itu terjadi.
Sayangnya tidak ada jawaban di bawah ini yang melakukan ini.

Andreas
sumber
2
Sebuah langkah baru pada masalah mendasar yang sama: Bagaimana cara menangkap STDOUT / STDERR yang dipesan dan menambahkan cap waktu / awalan?
Gilles 'SO- stop being evil'

Jawaban:

14

Bahkan tanpa redirection, atau dengan apa pun kecuali >logfile 2>&1, Anda tidak dijamin melihat output dalam urutan generasi.

Sebagai permulaan, stdout dari aplikasi akan menjadi buffer-line (to tty) atau buffered (ke pipeline) tetapi stderr tidak terganggu, sehingga hubungan antara urutan output terputus sejauh yang diperhatikan pembaca. Tahap-tahap berikutnya dalam pipa apa pun yang dapat Anda buat tidak akan mendapatkan akses yang terurut secara deterministik ke dua aliran (itu adalah hal-hal yang secara konseptual terjadi secara paralel, dan Anda selalu tunduk pada penjadwal - jika pada saat pembaca Anda mendapat sepotong, penulis sudah memiliki ditulis ke kedua pipa, Anda tidak bisa mengatakan mana yang lebih dulu).

"[T] dia memesan itu terjadi" hanya benar-benar diketahui aplikasi. Urutan output di stdout / stderr adalah masalah yang terkenal - klasik, mungkin -.

jmb
sumber
7

Bila Anda menggunakan konstruksi: 1>stdout.log 2>&1baik stderr dan stdout mendapatkan diarahkan ke file karena stdout pengalihan diatur sebelum stderr redirection.

Jika Anda membatalkan pesanan Anda bisa mendapatkan stdout diarahkan ke file dan kemudian salin stderr ke stdout sehingga Anda dapat mengirimnya tee.

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!
mmoya
sumber
Itu tidak memungkinkan Anda mencatat dua aliran ke file yang sama.
artistoex
1
@artistoex: Anda hanya dapat menggunakan tee -afile yang sama dengan yang digunakan 1>untuk menjumlahkan kedua output dalam file yang sama. Sesuatu seperti:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya
Saya tidak tahu mengapa, tetapi ini tidak berhasil wget -O - www.google.deuntuk saya. (bandingkan dengan solusi di bawah ini)
artistoex
4
Menggunakan tee -atidak akan bekerja andal untuk memiliki output yang sama dengan yang ada di konsol jika tidak ada yang dialihkan. Output yang masuk teemungkin sedikit tertunda dibandingkan dengan output yang datang langsung dari perintah, dan mungkin muncul nanti di log.
Gilles 'SO- stop being evil'
Ini tidak berfungsi untuk saya, keluar + err.log kosong. Dan ya, saya ingin stderror dan standar dalam file yang sama
Andreas
7

Saya dapat mencapai ini dengan menempatkan baris berikut di bagian atas skrip bash:

exec 1>>log 2> >(tee -a log >&2)

Ini akan mengarahkan stdout ke file log( 1>>log), lalu tee stderr ke file log( 2> >(tee -a log) dan mengarahkannya kembali ke stderr ( >&2). Dengan cara ini, saya mendapatkan satu file log,, yang menunjukkan urutan stdout dan stderr secara berurutan, dan stderr juga ditampilkan di layar seperti biasa.

Tangkapannya adalah bahwa itu hanya berfungsi ketika saya menambahkan file. Jika saya tidak menambahkan, tampaknya kedua pengalihan saling berkeping-keping, dan saya hanya mendapatkan satu keluaran terakhir.

bjg222
sumber
Adakah cara untuk mendapatkan ini untuk "memodifikasi" garis STDERR untuk memasukkan string khusus sehingga Anda dapat dengan mudah menerimanya?
IMTheNachoMan
1

Anda ingin menduplikasi aliran kesalahan sehingga muncul di konsol dan di file log. Alat untuk itu adalah tee, dan yang perlu Anda lakukan adalah menerapkannya ke aliran kesalahan. Sayangnya, tidak ada shell konstruksi standar untuk menyalurkan aliran kesalahan perintah ke perintah lain, sehingga diperlukan sedikit penataan ulang deskriptor file.

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Ini juga, tidak cukup untuk saya, saya mendapatkan baris stdout terlebih dahulu, dan kemudian semua baris stderr di file 'log'. contoh: {{echo out; echo err 1> & 2; echo out2; echo err2 1> & 2; } 2> & 1> & 3 | tee / dev / tty; }> log 3> & 1
Andreas
1
@ Andreas: Oh, ya, teememperkenalkan penundaan di sini juga. Saya tidak berpikir ada solusi shell murni. Sebenarnya saya bertanya-tanya apakah ada solusi tanpa memodifikasi aplikasi dengan cara tertentu.
Gilles 'SO- stop being evil'
1

Untuk menulis stderr ke layar dan menulis KEDUA stderr dan stdout ke file - DAN memiliki baris untuk stderr dan stdout keluar dalam urutan yang sama seperti jika keduanya ditulis ke layar:

Ternyata menjadi masalah yang sulit, terutama bagian tentang memiliki "urutan yang sama" yang Anda harapkan jika Anda hanya menulisnya di layar. Secara sederhana: Tulis masing-masing ke file sendiri, lakukan beberapa sihir proses latar belakang untuk menandai setiap baris (dalam setiap file) dengan waktu yang tepat garis itu diproduksi, dan kemudian: "ekor - ikuti" file stderr ke layar , tetapi untuk melihat KEDUA "stderr" dan "stdout" bersama - secara berurutan - urutkan kedua file (dengan tanda waktu yang tepat pada setiap baris) bersamaan.

Kode:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

Ya, ini tampaknya rumit, dan menghasilkan 4 file output (2 di antaranya dapat Anda hapus). Tampaknya ini adalah masalah yang sulit untuk dipecahkan, jadi butuh beberapa mekanisme.

Pada akhirnya, untuk melihat hasil dari KEDUA stdout dan stderr dalam urutan yang Anda harapkan, jalankan ini:

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

Satu-satunya alasan urutannya setidaknya sangat dekat dengan apa yang seharusnya dimiliki stdout dan stderr hanya pergi ke layar adalah: Setiap baris ditandai dengan cap waktu hingga milidetik.

Untuk melihat stderr di layar saat proses berjalan, gunakan ini:

tail -f $pth/$ffn.out

Harapan yang membantu seseorang keluar, yang tiba di sini lama setelah pertanyaan awal diajukan.

dana forsberg
sumber
1 untuk upaya semata-mata, saya punya ide yang sama (cap waktu kedua aliran melalui perl) tetapi berjuang untuk mengimplementasikannya. Saya akan menyelidiki apakah itu bekerja untuk saya (yaitu, jika itu benar-benar menjaga urutan output, karena solusi lain di sini menggeser hal-hal sedikit karena ketidaksinkronan antara penandaan stderr dan stdout)
Olivier Dulac
0

Biarkan fmenjadi perintah yang Anda ingin dieksekusi, maka ini

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

harus memberi Anda apa yang Anda inginkan. Sebagai contoh wget -O - www.google.deakan terlihat seperti ini:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )
artisoex
sumber
1
Ini hampir berhasil bagi saya, tetapi dalam file log saya pertama kali mendapatkan semua garis stdout, dan kemudian semua garis stderror. Saya ingin mereka disisipkan dalam tatanan alami saat itu terjadi.
Andreas
0

Mengingat apa yang saya baca di sini, satu-satunya solusi yang dapat saya lihat adalah dengan menambahkan setiap baris apakah STOUT atau STERR dengan timeslice / timestamp. date +% s akan menjadi detik, tapi itu lambat untuk mempertahankan pesanan. Log juga harus disortir. Lebih banyak pekerjaan daripada yang saya mampu.

orang tua yang lelah
sumber
0

Saya telah berjuang dengan persyaratan yang tepat ini, dan pada akhirnya tidak dapat menemukan solusi sederhana. Apa yang akhirnya saya lakukan adalah ini:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

Ini menambahkan stdout dan stderr, dalam urutan disisipkan tepat, ke file log. Dan jika ada kesalahan terjadi (sebagaimana ditentukan oleh status keluar dari perintah), ia mengirimkan seluruh output (stdout dan stderr) ke stdout, lagi-lagi dalam urutan disisipkan yang tepat. Saya menemukan ini sebagai kompromi yang masuk akal untuk kebutuhan saya. Jika Anda menginginkan file log sekali pakai daripada yang sedang tumbuh banyak file, itu bahkan lebih sederhana:

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log
Keith Pomakis
sumber
0

stderr ke perintah-diganti tee -a, dan stdout menambahkan ke file yang sama:

./script.sh 2> >(tee -a outputfile) >>outputfile

dan perhatikan: pastikan juga urutan yang benar (tetapi tanpa stderr-show) ada perintah 'unbuffer' dari alat 'expect', yang mensimulasikan tty dan menjaga stdout / err tetap seperti yang akan ditampilkan di terminal:

unbuffer ./script.sh > outputfile
Asain Kujovic
sumber