Bagaimana cara menulis stderr ke file saat menggunakan "tee" dengan pipa?

544

Aku tahu bagaimana menggunakan teeuntuk menulis output ( STDOUT) dari aaa.shke bbb.out, sementara masih menampilkan di terminal:

./aaa.sh | tee bbb.out

Bagaimana saya sekarang juga menulis STDERRke file bernama ccc.out, sementara masih ditampilkan?

jparanich
sumber
2
Untuk memperjelas - apakah Anda ingin stderr pergi ke layar serta file?
Charles Duffy
Saya lakukan, saya akan mengedit posting saya untuk mengklarifikasi itu. Saya yakin solusi lhunath akan cukup. Terima kasih atas bantuannya semua!
jparanich

Jawaban:

786

Saya berasumsi Anda ingin tetap melihat STDERR dan STDOUT di terminal. Anda bisa mencari jawaban Josh Kelley, tetapi saya menemukan taillatar belakang yang menghasilkan file log Anda sangat rapuh dan kasar. Perhatikan bagaimana Anda perlu menyimpan FD exra dan melakukan pembersihan setelah itu dengan membunuhnya dan secara teknis harus melakukan itu dalam trap '...' EXIT.

Ada cara yang lebih baik untuk melakukan hal ini, dan Anda sudah menemukannya: tee.

Hanya, alih-alih hanya menggunakannya untuk stdout Anda, miliki tee untuk stdout dan satu untuk stderr. Bagaimana Anda akan mencapai ini? Substitusi proses dan pengalihan file:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

Mari kita bagi dan jelaskan:

> >(..)

>(...)(substitusi proses) membuat FIFO dan mari kita teedengarkan. Kemudian, ia menggunakan >(pengalihan file) untuk mengarahkan kembali STDOUT commandke FIFO yang pertama kali Anda teedengarkan.

Hal yang sama untuk yang kedua:

2> >(tee -a stderr.log >&2)

Kami menggunakan subtitusi proses lagi untuk membuat teeproses yang membaca dari STDIN dan memasukkannya ke dalam stderr.log. teemengembalikan inputnya kembali ke STDOUT, tetapi karena inputnya adalah STDERR kami, kami ingin mengarahkan kembali teeSTDOUT ke STDERR kami. Kemudian kami menggunakan pengalihan file untuk mengarahkan kembali commandSTDERR ke input FIFO ( teeSTDIN).

Lihat http://mywiki.wooledge.org/BashGuide/InputAndOutput

Substitusi proses adalah salah satu dari hal-hal yang sangat menyenangkan yang Anda dapatkan sebagai bonus dengan memilih bashsebagai pengganti shell Anda sh(POSIX atau Bourne).


Di sh, Anda harus melakukan hal-hal secara manual:

out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"
lununath
sumber
5
Saya mencoba ini: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)yang berhasil, tetapi menunggu input. Apakah ada alasan sederhana mengapa ini terjadi?
Justin
1
@SillyFreak Saya tidak mengerti apa yang ingin Anda lakukan atau apa masalahnya. tes gema; exit tidak menghasilkan output apa pun di stdout, jadi err akan tetap kosong.
lhunath
1
terima kasih atas komentar itu; Saya menemukan apa kesalahan logis saya setelah itu: ketika dipanggil sebagai shell interaktif, bash mencetak prompt perintah dan gema keluar ke stderr. Namun, jika stderr dialihkan, bash dimulai sebagai non-aktif secara default; bandingkan /bin/bash 2> errdan/bin/bash -i 2> err
Silly Freak
15
Dan bagi mereka yang "melihat adalah percaya", ujian cepat:(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
Matthew Wilcoxson
2
Wow . Jawaban yang bagus: "Mari kita bagi dan jelaskan" +1
jalanb
673

mengapa tidak secara sederhana:

./aaa.sh 2>&1 | tee -a log

Ini hanya dialihkan stderrke stdout, jadi tee menggema untuk login dan ke layar. Mungkin saya kehilangan sesuatu, karena beberapa solusi lain tampaknya sangat rumit.

Catatan: Karena bash versi 4 Anda dapat menggunakan |&singkatan untuk 2>&1 |:

./aaa.sh |& tee -a log
pengguna542833
sumber
85
Itu berfungsi dengan baik jika Anda ingin stdout (saluran 1) dan stderr (saluran 2) masuk ke file yang sama (satu file yang berisi campuran stdout dan sterr). Solusi lain yang lebih rumit memungkinkan Anda memisahkan stdout dan stderr menjadi 2 file berbeda (stdout.log dan stderr.log, masing-masing). Terkadang itu penting, kadang tidak.
Tyler Rick
19
Solusi lain jauh lebih rumit daripada yang diperlukan dalam banyak kasus. Yang ini sangat cocok untuk saya.
dkamins
13
Masalah dengan metode ini adalah bahwa Anda kehilangan kode keluar / status dari proses aaa.sh, yang bisa menjadi penting (misalnya saat menggunakan dalam makefile). Anda tidak memiliki masalah dengan jawaban yang diterima.
Stefaan
9
jika Anda tidak keberatan digabung stdout / stderr kemudian ./aaa.sh |& tee aaa.logberfungsi (dalam bash).
jfs
5
@Stefaan Saya percaya Anda dapat mempertahankan status keluar jika Anda menambahkan rantai perintah dengan set -o pipefaildiikuti oleh ;atau &&jika saya tidak salah.
David
59

Ini mungkin berguna untuk orang yang menemukan ini melalui google. Hapus komentar contoh yang ingin Anda coba. Tentu saja, jangan ragu untuk mengganti nama file output.

#!/bin/bash

STATUSFILE=x.out
LOGFILE=x.log

### All output to screen
### Do nothing, this is the default


### All Output to one file, nothing to the screen
#exec > ${LOGFILE} 2>&1


### All output to one file and all output to the screen
#exec > >(tee ${LOGFILE}) 2>&1


### All output to one file, STDOUT to the screen
#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)


### All output to one file, STDERR to the screen
### Note you need both of these lines for this to work
#exec 3>&1
#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)


### STDOUT to STATUSFILE, stderr to LOGFILE, nothing to the screen
#exec > ${STATUSFILE} 2>${LOGFILE}


### STDOUT to STATUSFILE, stderr to LOGFILE and all output to the screen
#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)


### STDOUT to STATUSFILE and screen, STDERR to LOGFILE
#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}


### STDOUT to STATUSFILE, STDERR to LOGFILE and screen
#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)


echo "This is a test"
ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff
ls -l ${0}
Anthony
sumber
6
Tidak, dan saya kira eksekutif dapat menggunakan beberapa penjelasan. exec >artinya, pindahkan target deskriptor file ke tujuan tertentu. Defaultnya adalah 1, jadi, exec > /dev/nullpindahkan output dari stdout ke / dev / null mulai sekarang di sesi ini. Deskriptor file saat ini untuk sesi ini dapat dilihat dengan melakukan ls -l /dev/fd/. Cobalah! Kemudian lihat apa yang terjadi ketika Anda menerbitkan exec 2>/tmp/stderr.log.Selain itu, exec 3>&1berarti, membuat deskriptor file baru dengan nomor 3, dan mengarahkannya ke target deskriptor file 1. Pada contoh, target adalah layar ketika perintah dikeluarkan.
Drum
Contoh untuk memperlihatkan stdout dan stderr di layar dan memisahkan file-file itu luar biasa !!! Terima kasih banyak!
Marcello de Sales
21

Untuk mengarahkan ulang stderr ke file, tampilkan stdout ke layar, dan juga simpan stdout ke file:

./aaa.sh 2> ccc.out | tee ./bbb.out

EDIT : Untuk menampilkan stderr dan stdout ke layar dan juga menyimpan keduanya ke file, Anda dapat menggunakan pengalihan I / O bash :

#!/bin/bash

# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out

# Also print the contents of this file to screen.
tail -f ccc.out &

# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out

# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1
Josh Kelley
sumber
1
Saya berharap bahwa pengguna ingin stderr untuk pergi ke konsol mereka di samping file, meskipun itu tidak ditentukan secara eksplisit.
Charles Duffy
2
Seharusnya saya lebih jelas, saya ingin stderr ke layar juga. Saya masih menikmati solusi Josh Kelley tetapi menemukan lhunath lebih sesuai dengan kebutuhan saya. Terima kasih teman-teman!
jparanich
18

Dengan kata lain, Anda ingin mem-pipe stdout ke satu filter ( tee bbb.out) dan stderr ke filter lain ( tee ccc.out). Tidak ada cara standar untuk menyalurkan apa pun selain stdout ke perintah lain, tetapi Anda dapat menyiasatinya dengan menyulap file deskriptor.

{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2

Lihat juga Bagaimana cara menerima aliran kesalahan standar (stderr)? dan Kapan Anda akan menggunakan deskriptor file tambahan?

Di bash (dan ksh dan zsh), tetapi tidak di shell POSIX lain seperti tanda hubung, Anda dapat menggunakan subtitusi proses :

./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out)

Hati-hati bahwa dalam bash, perintah ini kembali segera setelah ./aaa.shselesai, bahkan jika teeperintah masih dijalankan (ksh dan zsh menunggu subproses). Ini mungkin masalah jika Anda melakukan sesuatu seperti ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. Dalam hal ini, gunakan juggling deskriptor file atau ksh / zsh sebagai gantinya.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
4
Ini sepertinya satu-satunya jawaban yang memungkinkan menjaga stdout / stderr stream apa adanya (mis. Tidak menggabungkannya). Manis!
user1338062
Pendekatan paling sederhana untuk sh, berguna untuk pekerjaan cron, di mana proses substitusi tidak tersedia.
Roger Dueck
13

Jika menggunakan bash:

# Redirect standard out and standard error separately
% cmd >stdout-redirect 2>stderr-redirect

# Redirect standard error and out together
% cmd >stdout-redirect 2>&1

# Merge standard error with standard out and pipe
% cmd 2>&1 |cmd2

Kredit (tidak menjawab dari atas kepala saya) ada di sini: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html

ChristopheD
sumber
5

Dalam kasus saya, skrip menjalankan perintah sambil mengarahkan stdout dan stderr ke file, seperti:

cmd > log 2>&1

Saya perlu memperbaruinya sehingga ketika ada kegagalan, mengambil beberapa tindakan berdasarkan pesan kesalahan. Tentu saja saya bisa menghapus dup 2>&1dan menangkap stderr dari skrip, tetapi kemudian pesan kesalahan tidak akan masuk ke file log untuk referensi. Sementara jawaban yang diterima dari @lhunath seharusnya melakukan hal yang sama, itu mengarahkan stdoutdan stderrke file yang berbeda, yang bukan yang saya inginkan, tetapi membantu saya untuk menemukan solusi tepat yang saya butuhkan:

(cmd 2> >(tee /dev/stderr)) > log

Dengan di atas, log akan memiliki salinan keduanya stdoutdan stderrdan saya dapat mengambil stderrdari skrip saya tanpa harus khawatir stdout.

haridsv
sumber
4

Berikut ini akan bekerja untuk KornShell (ksh) di mana proses substitusi tidak tersedia,

# create a combined(stdin and stdout) collector
exec 3 <> combined.log

# stream stderr instead of stdout to tee, while draining all stdout to the collector
./aaa.sh 2>&1 1>&3 | tee -a stderr.log 1>&3

# cleanup collector
exec 3>&-

Trik sebenarnya di sini, adalah urutan 2>&1 1>&3yang dalam kasus kami mengarahkan ulang stderrke stdoutdan mengarahkan stdoutke deskripsi 3. Pada titik ini stderrdanstdout belum digabungkan.

Akibatnya, stderr(as stdin) dilewatkan ke teetempat ia masukstderr.log dan juga pengalihan ke deskriptor 3.

Dan deskriptor 3mencatatnya combined.logsepanjang waktu. Jadi combined.logmengandung keduanyastdout dan stderr.

shuva
sumber
Bagus! Sayang sekali itu diremehkan karena itu pilihan yang bagus. Saya punya KSH, btw :)
runlevel0
2

Jika Anda menggunakan zsh , Anda dapat menggunakan beberapa pengalihan, sehingga Anda bahkan tidak perlu tee:

./cmd 1>&1 2>&2 1>out_file 2>err_file

Di sini Anda hanya mengarahkan setiap aliran ke dirinya sendiri dan file target.


Contoh lengkap

% (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
out
err
% cat /tmp/out_file
out
% cat /tmp/err_file
err

Perhatikan bahwa ini membutuhkan MULTIOSopsi yang akan ditetapkan (yang merupakan default).

MULTIOS

Lakukan implisit teeatau cats ketika banyak pengalihan dilakukan (lihat Pengalihan ).

Arminius
sumber