redirect COPY dari stdout ke file log dari dalam skrip bash itu sendiri

235

Saya tahu cara mengarahkan stdout ke file:

exec > foo.log
echo test

ini akan menempatkan 'test' ke dalam file foo.log.

Sekarang saya ingin mengarahkan output ke file log DAN tetap di stdout

yaitu dapat dilakukan secara sepele dari luar skrip:

script | tee foo.log

tapi saya ingin mendeklarasikannya dalam skrip itu sendiri

Saya mencoba

exec | tee foo.log

tapi itu tidak berhasil.

Vitaly Kushner
sumber
3
Pertanyaan Anda tidak diutarakan dengan baik. Saat Anda memanggil 'exec> foo.log', stdout skrip adalah file foo.log. Saya pikir Anda berarti bahwa Anda ingin output untuk pergi ke foo.log dan tty, karena akan foo.log yang akan stdout.
William Pursell
yang ingin saya lakukan adalah menggunakan | pada 'exec'. itu akan menjadi sempurna bagi saya, yaitu "exec | tee foo.log", sayangnya Anda tidak dapat menggunakan pengalihan pipa pada panggilan exec
Vitaly Kushner

Jawaban:

297
#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

Perhatikan bahwa ini bashbukan sh. Jika Anda memanggil skrip dengan sh myscript.sh, Anda akan mendapatkan kesalahan di sepanjang baris syntax error near unexpected token '>'.

Jika Anda bekerja dengan perangkap sinyal, Anda mungkin ingin menggunakan tee -iopsi untuk menghindari gangguan output jika sinyal terjadi. (Terima kasih kepada JamesThomasMoon1979 untuk komentarnya.)


Alat-alat yang mengubah output mereka tergantung pada apakah mereka menulis ke pipa atau terminal ( lsmenggunakan warna dan output berbentuk kolom, misalnya) akan mendeteksi konstruk di atas sebagai makna bahwa mereka output ke pipa.

Ada beberapa opsi untuk menerapkan pewarnaan / pengolomisasian (mis ls -C --color=always.). Perhatikan bahwa ini akan menghasilkan kode warna yang ditulis ke logfile juga, membuatnya kurang dapat dibaca.

DevSolar
sumber
5
Tee pada kebanyakan sistem buffered, jadi output mungkin tidak sampai sampai skrip selesai. Juga, karena tee ini berjalan dalam subkulit, bukan proses anak, tunggu tidak dapat digunakan untuk menyinkronkan output ke proses panggilan. Apa yang Anda inginkan adalah versi tee yang tidak dibuat
14
@ Larry: POSIX menentukan bahwa teeseharusnya tidak buffer outputnya. Jika buffer pada sebagian besar sistem, itu rusak pada sebagian besar sistem. Itu masalah teeimplementasi, bukan solusi saya.
DevSolar
3
@ Sebastian: execsangat kuat, tetapi juga sangat terlibat. Anda dapat "mencadangkan" stdout saat ini ke deskriptor yang berbeda, lalu memulihkannya nanti. Google "tutorial bash exec", ada banyak hal canggih di luar sana.
DevSolar
2
@AdamSpiers: Saya juga tidak yakin tentang apa itu Barry. Bash execadalah didokumentasikan untuk tidak memulai proses baru, >(tee ...)adalah standar pipa bernama / proses substitusi, dan &dalam pengalihan tentu saja tidak ada hubungannya dengan pelatarbelakangan ... :-)?
DevSolar
11
Saya sarankan lewat -iuntuk tee. Jika tidak, sinyal interupsi (jebakan) akan mengganggu stdout di skrip utama. Misalnya, jika Anda memiliki trap 'echo foo' EXITdan kemudian tekan ctrl+c, Anda tidak akan melihat " foo ". Jadi saya akan memodifikasi jawabannya exec &> >(tee -ia file).
JamesThomasMoon1979
174

Jawaban yang diterima tidak mempertahankan STDERR sebagai deskriptor file terpisah. Itu berarti

./script.sh >/dev/null

tidak akan keluaran barke terminal, hanya ke logfile, dan

./script.sh 2>/dev/null

akan menampilkan keduanya foodan barke terminal. Jelas itu bukan perilaku yang biasa diharapkan oleh pengguna normal. Ini dapat diperbaiki dengan menggunakan dua proses tee terpisah yang keduanya ditambahkan ke file log yang sama:

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(Perhatikan bahwa di atas pada awalnya tidak memotong file log - jika Anda ingin perilaku itu Anda harus menambahkan

>foo.log

ke bagian atas skrip.)

The Spesifikasi POSIX.1-2008 daritee(1) membutuhkan output yang unbuffered, yaitu tidak bahkan line-buffered, sehingga dalam hal ini adalah mungkin bahwa stdout dan stderr bisa berakhir pada baris yang sama dari foo.log; Namun itu juga bisa terjadi pada terminal, sehingga file log akan menjadi cerminan yang setia dari apa yang bisa dilihat di terminal, jika bukan cermin yang tepat dari itu. Jika Anda ingin garis-garis STDOUT dipisahkan dengan bersih dari garis-garis STDERR, pertimbangkan untuk menggunakan dua file log, mungkin dengan awalan cap tanggal pada setiap baris untuk memungkinkan penyusunan kembali kronologis nanti.

Adam Spires
sumber
Untuk beberapa alasan, dalam kasus saya, ketika skrip dieksekusi dari panggilan sistem-c (), dua sub-proses tee terus ada bahkan setelah skrip utama keluar. Jadi saya harus menambahkan jebakan seperti ini:exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
alveko
15
Saya sarankan lewat -iuntuk tee. Jika tidak, sinyal interupsi (jebakan) akan mengganggu stdout dalam skrip. Misalnya, jika Anda trap 'echo foo' EXITlalu tekan ctrl+c, Anda tidak akan melihat " foo ". Jadi saya akan memodifikasi jawabannya exec > >(tee -ia foo.log).
JamesThomasMoon1979
Saya membuat beberapa skrip "sourceable" kecil berdasarkan ini. Dapat menggunakannya dalam skrip seperti . logatau . log foo.log: sam.nipl.net/sh/log sam.nipl.net/sh/log-a
Sam Watkins
1
Masalah dengan metode ini adalah bahwa pesan akan STDOUTmuncul pertama kali sebagai batch, dan kemudian pesan akan STDERRmuncul. Mereka tidak disisipkan seperti yang diharapkan.
CMCDragonkai
28

Solusi untuk busybox, macOS bash, dan cangkang non-bash

Jawaban yang diterima tentu saja merupakan pilihan terbaik untuk bash. Saya bekerja di lingkungan Busybox tanpa akses ke bash, dan itu tidak mengerti exec > >(tee log.txt)sintaks. Itu juga tidak melakukan exec >$PIPEdengan benar, mencoba membuat file biasa dengan nama yang sama dengan pipa bernama, yang gagal dan hang.

Semoga ini bermanfaat bagi orang lain yang tidak memiliki bash.

Juga, bagi siapa saja yang menggunakan pipa bernama, aman untuk itu rm $PIPE, karena itu memutuskan tautan pipa dari VFS, tetapi proses yang menggunakannya masih mempertahankan jumlah referensi di atasnya sampai selesai.

Perhatikan penggunaan $ * belum tentu aman.

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here
jbarlow
sumber
Ini adalah satu-satunya solusi yang saya lihat sejauh ini yang bekerja pada mac
Mike Baglio Jr
19

Di dalam file skrip Anda, masukkan semua perintah di dalam tanda kurung, seperti ini:

(
echo start
ls -l
echo end
) | tee foo.log
WReach
sumber
5
pedantically, bisa juga menggunakan kawat gigi ( {})
glenn jackman
baik ya, saya menganggap itu, tapi ini bukan pengalihan shell stdout saat ini, jenis cheat, Anda benar-benar menjalankan subkulit dan melakukan pengalihan piper biasa di atasnya. pikir bekerja. Saya terpecah dengan ini dan solusi "tail -f foo.log &". akan menunggu sedikit untuk melihat apakah mungkin permukaan yang lebih baik. jika tidak mungkin akan puas;)
Vitaly Kushner
8
{} mengeksekusi daftar di lingkungan shell saat ini. () mengeksekusi daftar di lingkungan subkulit.
Sial. Terima kasih. Jawaban yang diterima di sana tidak berhasil bagi saya, mencoba menjadwalkan skrip untuk dijalankan di bawah MingW pada sistem Windows. Saya yakin itu mengeluh tentang substitusi proses yang tidak diterapkan. Jawaban ini bekerja dengan baik, setelah perubahan berikut, untuk menangkap stderr dan stdout: `` `-) | tee foo.log +) 2> & 1 | tee foo.log
Jon Carter
14

Cara mudah untuk membuat log skrip bash ke syslog. Keluaran skrip tersedia melalui /var/log/syslogdan melalui stderr. syslog akan menambahkan metadata yang berguna, termasuk cap waktu.

Tambahkan baris ini di atas:

exec &> >(logger -t myscript -s)

Atau, kirim log ke file terpisah:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

Ini membutuhkan moreutils(untuk tsperintah, yang menambahkan cap waktu).

Tobu
sumber
10

Menggunakan jawaban yang diterima skrip saya terus kembali sangat awal (tepat setelah 'exec>> (tee ...)') meninggalkan sisa skrip saya berjalan di latar belakang. Karena saya tidak bisa mendapatkan solusi untuk bekerja dengan cara saya, saya menemukan solusi lain / menyelesaikan masalah:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

Ini membuat output dari skrip beralih dari proses, melalui pipa ke proses sub-latar belakang 'tee' yang mencatat semuanya ke disk dan ke stdout asli skrip.

Perhatikan bahwa 'exec &>' mengalihkan stdout dan stderr, kita dapat mengarahkan mereka secara terpisah jika kita mau, atau mengubah ke 'exec>' jika kita hanya ingin stdout.

Bahkan kamu pipa dihapus dari sistem file di awal skrip itu akan terus berfungsi sampai proses selesai. Kami tidak dapat merujuknya menggunakan nama file setelah rm-line.

fgunger
sumber
Jawaban yang sama sebagai ide kedua dari David Z . Lihatlah komentarnya. +1 ;-)
olibre
Bekerja dengan baik. Saya tidak mengerti $logfilebagian dari tee < ${logfile}.pipe $logfile &. Secara khusus, saya mencoba mengubah ini untuk menangkap baris log perintah yang diperluas penuh (dari set -x) ke file sementara hanya menampilkan baris tanpa memimpin '+' di stdout dengan mengubah (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &tetapi menerima pesan kesalahan mengenai $logfile. Bisakah Anda menjelaskannya teesedikit lebih detail?
Chris Johnson
Saya menguji ini dan tampaknya jawaban ini tidak mempertahankan STDERR (digabung dengan STDOUT), jadi jika Anda mengandalkan stream yang terpisah untuk deteksi kesalahan atau pengalihan lainnya, Anda harus melihat jawaban Adam.
HeroCC
2

Bash 4 memiliki coprocperintah yang menetapkan pipa bernama ke perintah dan memungkinkan Anda untuk berkomunikasi melalui itu.

Dijeda sampai pemberitahuan lebih lanjut.
sumber
1

Tidak bisa mengatakan saya nyaman dengan salah satu solusi yang berbasis pada exec. Saya lebih suka menggunakan tee secara langsung, jadi saya membuat script memanggil dirinya sendiri dengan tee ketika diminta:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

Ini memungkinkan Anda untuk melakukan ini:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

Anda dapat menyesuaikan ini, misalnya membuat tee = false sebagai gantinya, membuat TEE menyimpan file log, dll. Saya kira solusi ini mirip dengan jbarlow, tetapi lebih sederhana, mungkin tambang saya memiliki batasan yang belum saya temui.

Oliver
sumber
-1

Tidak satu pun dari ini adalah solusi yang sempurna, tetapi di sini ada beberapa hal yang bisa Anda coba:

exec >foo.log
tail -f foo.log &
# rest of your script

atau

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

Yang kedua akan meninggalkan file pipa duduk-duduk jika ada yang salah dengan skrip Anda, yang mungkin atau mungkin tidak menjadi masalah (yaitu mungkin Anda bisa rmmelakukannya di shell induk setelahnya).

David Z
sumber
1
tail akan meninggalkan proses yang berjalan di belakang dalam tee script ke-2 akan memblokir, atau Anda harus menjalankannya dengan & dalam hal ini akan meninggalkan proses seperti pada yang ke-1.
Vitaly Kushner
@Vitaly: oops, lupa latar belakang tee- Saya sudah mengedit. Seperti yang saya katakan, tidak ada solusi yang sempurna, tetapi proses latar belakang akan terbunuh ketika shell induknya berakhir, jadi Anda tidak perlu khawatir tentang mereka memonopoli sumber daya selamanya.
David Z
1
Yikes: ini terlihat menarik, tetapi output dari tail -f juga akan menjadi foo.log. Anda dapat memperbaikinya dengan menjalankan tail -f sebelum eksekutif, tetapi ekor masih berjalan setelah induk berhenti. Anda perlu secara eksplisit membunuhnya, mungkin dalam perangkap 0.
William Pursell
Yap. Jika skrip dilatar belakangi, skrip ini meninggalkan semua proses.