Bagaimana cara mengarahkan output dari seluruh skrip shell di dalam skrip itu sendiri?

205

Apakah mungkin untuk mengalihkan semua output skrip Bourne shell ke suatu tempat, tetapi dengan perintah shell di dalam skrip itu sendiri?

Mengarahkan output dari satu perintah itu mudah, tetapi saya ingin sesuatu yang lebih seperti ini:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Artinya: jika skrip dijalankan non-interaktif (misalnya, cron), simpan output dari segalanya ke file. Jika dijalankan secara interaktif dari shell, biarkan output pergi ke stdout seperti biasa.

Saya ingin melakukan ini untuk skrip yang biasanya dijalankan oleh utilitas periodik FreeBSD. Itu adalah bagian dari kegiatan harian, yang biasanya tidak saya perhatikan setiap hari melalui email, jadi saya tidak mengirimkannya. Namun, jika sesuatu di dalam skrip yang satu ini gagal, itu penting bagi saya dan saya ingin dapat menangkap dan mengirim email output dari bagian pekerjaan harian ini.

Pembaruan: Jawaban Joshua tepat, tetapi saya juga ingin menyimpan dan memulihkan stdout dan stderr di seluruh skrip, yang dilakukan seperti ini:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4
Steve Madsen
sumber
2
Menguji $ TERM bukan cara terbaik untuk menguji mode interaktif. Sebagai gantinya, uji apakah stdin adalah tty (tes -t 0).
Chris Jester-Young
2
Dengan kata lain: if [! -t 0]; kemudian exec> somefile 2> & 1; fi
Chris Jester-Young
1
Lihat di sini untuk semua kebaikannya: http://tldp.org/LDP/abs/html/io-redirection.html Pada dasarnya apa yang dikatakan oleh Joshua. exec> pengalihan file stdout ke file tertentu, exec <file menggantikan stdin dengan file, dll. Sama seperti biasanya tetapi menggunakan exec (lihat man exec untuk lebih jelasnya).
Loki
Di bagian pembaruan Anda, Anda juga harus menutup FD 3 dan 4, seperti: exec 1>&3 2>&4 3>&- 4>&-
Gurjeet Singh

Jawaban:

173

Mengatasi pertanyaan saat diperbarui.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

Kawat gigi '{...}' menyediakan unit pengalihan I / O. Kawat gigi harus muncul di mana perintah dapat muncul - secara sederhana, pada awal baris atau setelah titik koma. ( Ya, itu bisa dibuat lebih tepat; jika Anda ingin berdalih, beri tahu saya. )

Anda benar bahwa Anda dapat mempertahankan stdout asli dan stderr dengan pengalihan yang Anda tunjukkan, tetapi biasanya lebih mudah bagi orang-orang yang harus mempertahankan skrip nanti untuk memahami apa yang terjadi jika Anda lingkup kode diarahkan seperti yang ditunjukkan di atas.

Bagian yang relevan dari manual Bash yang Pengelompokan Perintah dan I / O Redirection . Bagian yang relevan dari spesifikasi shell POSIX adalah Compound Commands dan I / O Redirection . Bash memiliki beberapa notasi tambahan, tetapi sebaliknya mirip dengan spesifikasi shell POSIX.

Jonathan Leffler
sumber
3
Ini jauh lebih jelas daripada menyimpan deskriptor asli dan mengembalikannya nanti.
Steve Madsen
23
Saya harus melakukan beberapa googling untuk memahami apa yang sebenarnya dilakukan, jadi saya ingin berbagi. Kurung kurawal menjadi "blok kode" , yang, pada dasarnya, menciptakan fungsi anonim . Semua output dalam blok kode kemudian dapat dialihkan (Lihat Contoh 3-2 dari tautan itu). Perhatikan juga bahwa kurung kurawal tidak meluncurkan subkulit , tetapi pengalihan I / O serupa dapat dilakukan dengan subkulit menggunakan tanda kurung.
chris
3
Saya suka solusi ini lebih baik daripada yang lain. Bahkan seseorang dengan hanya pemahaman paling dasar tentang pengalihan I / O dapat memahami apa yang terjadi. Plus, itu lebih bertele-tele. Dan, sebagai Pythoner, saya suka verbose.
John Red
Lebih baik lakukan >>. Beberapa orang punya kebiasaan >. Menambahkan selalu lebih aman dan lebih direkomendasikan daripada menghabiskan waktu. Seseorang menulis aplikasi yang menggunakan perintah salin standar untuk mengekspor beberapa data ke tujuan yang sama.
neverMind9
Aplikasi itu adalah ccc.bmw71 (.pro) (Widget Monitor Baterai 3C, sebelumnya "Widget Monitor Baterai") selalu mengekspor riwayat baterai ke /sdcard/bmw_history.txt. Jika sudah ada, coba tebak, OVERWRITE! Ini membuat saya secara tidak sengaja kehilangan beberapa riwayat baterai. Saya menetapkan batas dalam beberapa hari dari 30 ke angka yang sangat tinggi, yang membatalkannya dan mengembalikannya ke 30. Saya ingin mengekspor riwayat baterai saat ini sebelum mengimpor cadangan yang ada. Lalu itu terjadi.
neverMind9
175

Biasanya kami akan menempatkan salah satunya di atau dekat bagian atas skrip. Skrip yang menguraikan baris perintah mereka akan melakukan pengalihan setelah penguraian.

Kirim stdout ke file

exec > file

dengan stderr

exec > file                                                                      
exec 2>&1

tambahkan stdout dan stderr ke file

exec >> file
exec 2>&1

Seperti yang Jonathan Leffler katakan dalam komentarnya :

execmemiliki dua pekerjaan terpisah. Yang pertama adalah mengganti shell (skrip) yang saat ini dieksekusi dengan program baru. Yang lainnya adalah mengubah pengalihan I / O di shell saat ini. Ini dibedakan dengan tidak memiliki argumen exec.

Joshua
sumber
7
Saya katakan juga tambahkan 2> & 1 di akhir itu, supaya stderr tertangkap juga. :-)
Chris Jester-Young
7
Di mana Anda meletakkan ini? Di bagian atas skrip?
colan
4
Dengan solusi ini kita juga harus mengatur ulang pengalihan script. Jawaban berikutnya oleh Jonathan Leffler lebih "gagal bukti" dalam pengertian ini.
Chuim
6
@ JohnRed: execmemiliki dua pekerjaan terpisah. Salah satunya adalah mengganti skrip saat ini dengan perintah lain, menggunakan proses yang sama - Anda menentukan perintah lain sebagai argumen untuk exec(dan Anda dapat men-tweak pengalihan I / O saat Anda melakukannya). Pekerjaan lainnya adalah mengubah pengalihan I / O dalam skrip shell saat ini tanpa menggantinya. Notasi ini dibedakan dengan tidak memiliki perintah sebagai argumen exec. Notasi dalam jawaban ini adalah varian "I / O only" - hanya mengubah pengalihan dan tidak menggantikan skrip yang sedang berjalan. ( setPerintahnya juga multi-guna.)
Jonathan Leffler
18
exec > >(tee -a "logs/logdata.log") 2>&1mencetak log di layar dan juga menuliskannya ke dalam file
shriyog
32

Anda dapat menjadikan seluruh skrip sebagai fungsi seperti ini:

main_function() {
  do_things_here
}

maka pada akhir skrip miliki ini:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

Atau, Anda dapat mencatat semuanya ke dalam logfile setiap kali dijalankan dan masih menampilkannya ke stdout dengan hanya melakukan:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log
dbguy
sumber
Apakah maksud Anda main_fungsi >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez
Saya suka menggunakan main_function di pipa tersebut. Tetapi dalam kasus ini skrip Anda tidak mengembalikan nilai pengembalian asli. Dalam kasus bash, Anda harus keluar kemudian menggunakan 'keluar $ {PIPESTATUS [0]}'.
rudimeier
7

Untuk menyimpan stdout dan stderr asli, Anda dapat menggunakan:

exec [fd number]<&1 
exec [fd number]<&2

Misalnya, kode berikut akan mencetak "walla1" dan "walla2" ke file log ( a.txt), "walla3" ke stdout, "walla4" ke stderr.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6
Eyal leshem
sumber
1
Biasanya, akan lebih baik untuk menggunakan exec 5>&1dan exec 6>&2, menggunakan notasi redirection output daripada notasi redirection input untuk output. Anda lolos begitu saja karena ketika skrip dijalankan dari terminal, input standar juga dapat ditulis dan baik output standar dan kesalahan standar dapat dibaca berdasarkan (atau apakah itu 'wakil'?) Dari kekhasan sejarah: terminal dibuka untuk membaca dan menulis dan deskripsi file terbuka yang sama digunakan untuk ketiga deskriptor file I / O standar.
Jonathan Leffler
3
[ -t <&0 ] || exec >> test.log
Dimitar
sumber