Bagaimana saya bisa memasang stderr, dan tidak stdout?

982

Saya memiliki program yang menulis informasi untuk stdoutdan stderr, dan saya harus grepmelalui apa yang akan terjadi pada stderr , sambil mengabaikan stdout .

Tentu saja saya bisa melakukannya dalam 2 langkah:

command > /dev/null 2> temp.file
grep 'something' temp.file

tapi saya lebih suka bisa melakukan ini tanpa file temp. Apakah ada trik perpipaan yang cerdas?

Peter Mortensen
sumber
Pertanyaan serupa, tetapi tetap stdout: unix.stackexchange.com/questions/3514/…
joeytwiddle
Pertanyaan ini untuk Bash tetapi perlu menyebutkan artikel terkait ini untuk shell Bourne / Almquist.
Stephen Niedzielski
10
Saya mengharapkan sesuatu seperti ini: command 2| othercommand. Bash sangat sempurna sehingga pengembangan berakhir pada 1982, jadi kami tidak akan pernah melihatnya di bash, aku khawatir.
Rolf

Jawaban:

1190

Redirect stderr pertama ke stdout - pipa; lalu arahkan stdout ke /dev/null(tanpa mengubah arah tujuan stderr):

command 2>&1 >/dev/null | grep 'something'

Untuk detail pengalihan I / O dalam semua varietasnya, lihat bab Pengalihan di manual referensi Bash.

Perhatikan bahwa urutan pengalihan I / O ditafsirkan dari kiri ke kanan, tetapi pipa disiapkan sebelum pengalihan I / O ditafsirkan. Deskriptor file seperti 1 dan 2 adalah referensi untuk membuka deskripsi file. Operasi ini 2>&1membuat file descriptor 2 alias stderr merujuk ke deskripsi file terbuka yang sama dengan file descriptor 1 alias stdout saat ini merujuk ke (lihat dup2()dan open()). Operasi >/dev/nullkemudian mengubah file descriptor 1 sehingga mengacu pada deskripsi file terbuka /dev/null, tetapi itu tidak mengubah fakta bahwa file deskriptor 2 mengacu pada deskripsi file terbuka yang file deskriptor 1 awalnya tunjuk - yaitu, pipa.

Jonathan Leffler
sumber
44
Saya baru saja menemukan / dev / stdout / dev / stderr / dev / stdin tempo hari, dan saya ingin tahu apakah itu cara yang baik untuk melakukan hal yang sama? Saya selalu berpikir 2 & 1 agak dikaburkan. Jadi sesuatu seperti: command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons
17
Anda bisa menggunakan /dev/stdoutet al, atau menggunakan /dev/fd/N. Mereka akan sedikit kurang efisien kecuali jika shell memperlakukan mereka sebagai kasus khusus; notasi angka murni tidak melibatkan mengakses file dengan nama, tetapi menggunakan perangkat berarti pencarian nama file. Apakah Anda bisa mengukurnya masih bisa diperdebatkan. Saya suka ringkasnya notasi angka - tetapi saya sudah menggunakannya begitu lama (lebih dari seperempat abad; aduh!) Sehingga saya tidak memenuhi syarat untuk menilai manfaatnya di dunia modern.
Jonathan Leffler
23
@ Jonathan Leffler: Saya mengambil sedikit masalah dengan penjelasan teks biasa Anda 'Redirect stderr ke stdout dan kemudian stdout ke / dev / null' - Karena kita harus membaca rantai pengalihan dari kanan ke kiri (bukan dari kiri ke kanan), kami harus juga menyesuaikan penjelasan teks biasa kami dengan ini: 'Redirect stdout ke / dev / null, dan kemudian stderr ke tempat stdout dulu' .
Kurt Pfeifle
116
@KurtPfeifle: au contraire! Seseorang harus membaca rantai pengalihan dari kiri ke kanan karena itulah cara shell memprosesnya. Operasi pertama adalah 2>&1, yang berarti 'hubungkan stderr ke deskriptor file yang saat ini akan stdout '. Operasi kedua adalah 'ubah stdout jadi begini /dev/null', meninggalkan stderr pergi ke stdout asli, pipa. Shell membagi sesuatu pada simbol pipa terlebih dahulu, jadi, pengalihan pipa terjadi sebelum 2>&1atau >/dev/nullredirection, tapi itu saja; operasi lainnya dari kiri ke kanan. (Kanan-ke-kiri tidak akan berfungsi.)
Jonathan Leffler
14
Hal yang benar-benar mengejutkan saya tentang ini adalah bahwa ia bekerja pada Windows juga (setelah berganti nama /dev/nullmenjadi Windows yang setara, nul).
Michael Burr
364

Atau untuk menukar output dari kesalahan standar dan output standar, gunakan:

command 3>&1 1>&2 2>&3

Ini menciptakan deskriptor file baru (3) dan menempatkannya di tempat yang sama dengan 1 (output standar), kemudian menetapkan fd 1 (output standar) ke tempat yang sama dengan fd 2 (kesalahan standar) dan akhirnya menetapkan fd 2 (kesalahan standar) ) ke tempat yang sama dengan fd 3 (output standar).

Kesalahan standar sekarang tersedia sebagai keluaran standar dan keluaran standar lama dipertahankan dalam kesalahan standar. Ini mungkin berlebihan, tetapi semoga memberikan lebih banyak detail pada deskriptor file Bash (ada sembilan tersedia untuk setiap proses).

Kramish
sumber
100
Tweak terakhir adalah 3>&-menutup deskriptor cadangan yang Anda buat dari stdout
Jonathan Leffler
1
Bisakah kita membuat file deskriptor yang dimiliki stderrdan yang lain memiliki kombinasi dari stderrdan stdout? Dengan kata lain dapatkah stderrpergi ke dua file berbeda sekaligus?
Stuart
Berikut ini masih mencetak kesalahan ke stdout. Apa yang saya lewatkan? ls-l not_a_file 3> & 1 1> & 2 2> & 3> errors.txt
user48956
1
@ JonasDahlbæk: tweak adalah masalah kerapian. Dalam situasi yang benar-benar misterius, mungkin membuat perbedaan antara proses mendeteksi dan tidak mendeteksi EOF, tetapi itu membutuhkan keadaan yang sangat aneh.
Jonathan Leffler
1
Perhatian : ini mengasumsikan FD 3 belum digunakan, tidak menutupnya, dan tidak membatalkan pertukaran file deskriptor 1 dan 2, jadi Anda tidak dapat melanjutkan untuk mem-pipe ini ke perintah lain. Lihat jawaban ini untuk perincian lebih lanjut dan penyelesaiannya. Untuk sintaks yang lebih bersih untuk {ba, z} sh, lihat jawaban ini .
Tom Hale
218

Di Bash, Anda juga bisa mengarahkan ke subkulit menggunakan subtitusi proses :

command > >(stdlog pipe)  2> >(stderr pipe)

Untuk kasus yang dihadapi:

command 2> >(grep 'something') >/dev/null
Rich Johnson
sumber
1
Berfungsi sangat baik untuk output ke layar. Apakah Anda tahu mengapa konten ungrepped muncul lagi jika saya mengarahkan output grep ke file? Setelah command 2> >(grep 'something' > grep.log)grep.log berisi output yang sama seperti ungrepped.log daricommand 2> ungrepped.log
Tim
9
Gunakan 2> >(stderr pipe >&2). Kalau tidak, output dari "pipa stderr" akan melalui "pipa stdlog".
ceving
ya ! 2> >(...), berhasil, saya mencoba 2>&1 > >(...)tetapi tidak
datdinhquoc
Berikut adalah contoh kecil yang dapat membantu saya lain kali ketika saya mencari cara melakukan ini. Pertimbangkan yang berikut ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) Dalam contoh ini saya ingin juga melihat apa yang keluar sebagai kesalahan pada konsol saya. Tetapi STDOUT pergi ke file output. Jadi di dalam sub-shell, Anda perlu mengarahkan STDOUT itu kembali ke STDERR di dalam tanda kurung. Sementara itu berfungsi, output STDOUT dari teeperintah berakhir di akhir out-content.txtfile. Itu tampaknya tidak konsisten bagi saya.
akan
@datdinhquoc Saya melakukannya entah bagaimana seperti2>&1 1> >(dest pipe)
Alireza Mohamadi
195

Menggabungkan yang terbaik dari jawaban ini, jika Anda melakukannya:

command 2> >(grep -v something 1>&2)

... maka semua stdout dipertahankan sebagai stdout dan semua stderr dipertahankan sebagai stderr, tetapi Anda tidak akan melihat baris di stderr yang berisi string "sesuatu".

Ini memiliki keuntungan unik yaitu tidak membalikkan atau membuang stdout dan stderr, atau menyatukannya, atau menggunakan file sementara.

Orang kiri Komunis
sumber
Bukankah command 2> >(grep -v something)(tanpa 1>&2) sama?
Francesc Rosas
11
Tidak, tanpa itu, stderr yang difilter akhirnya dialihkan ke stdout.
Pinko
1
Inilah yang saya butuhkan - tar keluaran "file berubah saat kita membacanya" untuk direktori selalu, jadi hanya ingin menyaring satu baris itu tetapi melihat apakah ada kesalahan lain terjadi. Jadi tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)harusnya bekerja.
Diruntuhkan
salah mengatakan "diawali dengan string". Tidak ada tentang sintaks yang disajikan grep yang akan membuatnya hanya mengecualikan baris yang dimulai dengan string yang diberikan. Baris akan dikecualikan jika mengandung string yang diberikan di mana saja di dalamnya.
Mike Nakis
@MikeNakis terima kasih - diperbaiki! (Itu adalah sisa dari konsep jawaban asli saya, yang masuk akal ...)
Pinko
102

Jauh lebih mudah untuk memvisualisasikan hal-hal jika Anda berpikir tentang apa yang sebenarnya terjadi dengan "pengalihan" dan "pipa." Arahan ulang dan pipa dalam bash melakukan satu hal: memodifikasi tempat file deskriptor 0, 1, dan 2 menunjuk ke (lihat / proc / [pid] / fd / *).

Ketika sebuah pipa atau "|" operator hadir pada baris perintah, hal pertama yang terjadi adalah bahwa bash menciptakan fifo dan mengarahkan FD1 perintah sisi kiri ke fifo ini, dan mengarahkan FD 0 perintah sisi kanan ke fifo yang sama.

Selanjutnya, operator pengalihan untuk setiap sisi dievaluasi dari kiri ke kanan , dan pengaturan saat ini digunakan setiap kali duplikasi deskriptor terjadi. Ini penting karena sejak pipa dipasang pertama kali, FD1 (sisi kiri) dan FD0 (sisi kanan) sudah berubah dari yang biasanya, dan duplikasi ini akan mencerminkan fakta itu.

Karena itu, ketika Anda mengetikkan sesuatu seperti berikut:

command 2>&1 >/dev/null | grep 'something'

Inilah yang terjadi, secara berurutan:

  1. sebuah pipa (fifo) dibuat. "command FD1" diarahkan ke pipa ini. "grep FD0" juga menunjuk ke pipa ini
  2. "command FD2" diarahkan ke tempat "command FD1" saat ini menunjuk (pipa)
  3. "perintah FD1" menunjuk ke / dev / null

Jadi, semua output yang "perintah" tulis ke FD 2 (stderr) membuat jalan ke pipa dan dibaca oleh "grep" di sisi lain. Semua output yang "perintah" tulis ke FD 1 (stdout) menuju ke / dev / null.

Jika sebaliknya, Anda menjalankan yang berikut:

command >/dev/null 2>&1 | grep 'something'

Inilah yang terjadi:

  1. sebuah pipa dibuat dan "perintah FD 1" dan "grep FD 0" diarahkan ke sana
  2. "perintah FD 1" diarahkan ke / dev / null
  3. "command FD 2" diarahkan ke tempat FD 1 saat ini menunjuk (/ dev / null)

Jadi, semua stdout dan stderr dari "command" menuju ke / dev / null. Tidak ada yang masuk ke pipa, dan dengan demikian "grep" akan menutup tanpa menampilkan apa pun di layar.

Perhatikan juga bahwa pengalihan (deskriptor file) dapat berupa read-only (<), only-write (>), atau read-write (<>).

Catatan terakhir. Apakah program menulis sesuatu ke FD1 atau FD2, sepenuhnya tergantung pada programmer. Praktik pemrograman yang baik menentukan bahwa pesan kesalahan harus masuk ke FD 2 dan output normal ke FD 1, tetapi Anda akan sering menemukan pemrograman yang ceroboh yang menggabungkan keduanya atau mengabaikan konvensi.

Michael Martinez
sumber
6
Jawaban yang sangat bagus. Satu saran saya adalah mengganti penggunaan pertama "fifo" dengan "fifo (pipa bernama)". Saya telah menggunakan Linux untuk sementara waktu tetapi entah bagaimana tidak pernah berhasil belajar bahwa itu adalah istilah lain untuk pipa bernama. Ini akan menyelamatkan saya dari mencari, tapi sekali lagi saya tidak akan belajar hal-hal lain yang saya lihat ketika saya tahu itu!
Mark Edington
3
@ MarkEdington Harap dicatat bahwa FIFO hanyalah istilah lain untuk pipa bernama dalam konteks pipa dan IPC . Dalam konteks yang lebih umum, FIFO berarti masuk pertama, keluar pertama, yang menjelaskan penyisipan dan penghapusan dari struktur data antrian.
Loomchild
5
@ Loomchild Tentu saja. Inti dari komentar saya adalah bahwa bahkan sebagai pengembang berpengalaman, saya belum pernah melihat FIFO digunakan sebagai sinonim untuk pipa bernama. Dengan kata lain, saya tidak tahu ini: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Mengklarifikasi bahwa dalam jawaban itu akan menghemat waktu saya.
Mark Edington
39

Jika Anda menggunakan Bash, gunakan:

command >/dev/null |& grep "something"

http://www.gnu.org/software/bash/manual/bashref.html#Pipelines

Ken Sharp
sumber
4
Tidak, |&sama dengan 2>&1yang menggabungkan stdout dan stderr. Pertanyaan secara eksplisit meminta hasil tanpa stdout.
Profpatsch
3
„Jika '| &' digunakan, kesalahan standar dari command1 terhubung ke input standar command2 melalui pipa; ini adalah singkatan untuk 2> & 1 | ” Diambil kata demi kata dari paragraf keempat di tautan Anda.
Profpatsch
9
@Profpatsch: Jawaban Ken benar, lihat bahwa ia mengarahkan ulang stdout ke nol sebelum menggabungkan stdout dan stderr, jadi Anda hanya akan masuk ke pipa stderr, karena stdout sebelumnya turun ke / dev / null.
Luciano
3
Tapi saya masih menemukan jawaban Anda salah, >/dev/null |&memperluas ke >/dev/null 2>&1 | dan berarti inode stdout kosong untuk pipa karena tidak ada (# 1 # 2 keduanya terkait dengan / dev / null inode) diikat ke inode stdout (misalnya ls -R /tmp/* >/dev/null 2>&1 | grep iakan memberikan kosong, tetapi ls -R /tmp/* 2>&1 >/dev/null | grep iakan membiarkan # 2 yang diikat ke inode stdout akan pipa).
Buah
3
Ken Sharp, saya menguji, dan ( echo out; echo err >&2 ) >/dev/null |& grep "."tidak memberikan output (di mana kami ingin "err"). man bashmengatakan Jika | & digunakan ... adalah singkatan untuk 2> & 1 |. Pengalihan implisit kesalahan standar ke output standar dilakukan setelah setiap pengalihan ditentukan oleh perintah. Jadi pertama-tama kita mengarahkan FD1 perintah ke nol, lalu kita mengarahkan FD2 perintah ke tempat FD1 menunjuk, yaitu. null, jadi FD0 grep tidak mendapat input. Lihat stackoverflow.com/a/18342079/69663 untuk penjelasan yang lebih mendalam.
Unhammer
11

Bagi mereka yang ingin mengarahkan stdout dan stderr secara permanen ke file, grep on stderr, tetapi tetap stdout untuk menulis pesan ke tty:

# save tty-stdout to fd 3
exec 3>&1
# switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
exec 2> >(grep -v "nasty_msg" >> std.err) >> std.out
# goes to the std.out
echo "my first message" >&1
# goes to the std.err
echo "a error message" >&2
# goes nowhere
echo "this nasty_msg won't appear anywhere" >&2
# goes to the tty
echo "a message on the terminal" >&3
JBD
sumber
6

Ini akan mengarahkan command1 stderr ke command2 stdin, sambil membiarkan command1 stdout apa adanya.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Diambil dari LDP

lumba - lumba
sumber
2

Saya baru saja menemukan solusi untuk mengirim stdoutke satu perintah dan stderrke yang lain, menggunakan pipa bernama.

Ini dia.

mkfifo stdout-target
mkfifo stderr-target
cat < stdout-target | command-for-stdout &
cat < stderr-target | command-for-stderr &
main-command 1>stdout-target 2>stderr-target

Mungkin ide yang bagus untuk menghapus pipa bernama sesudahnya.

Kinetika Tripp
sumber
0

Anda dapat menggunakan shell rc .

Pertama instal paket (kurang dari 1 MB).

Ini contoh bagaimana Anda akan membuang output standar dan pipa standard error untuk grep di rc:

find /proc/ >[1] /dev/null |[2] grep task

Anda dapat melakukannya tanpa meninggalkan Bash:

rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

Seperti yang mungkin telah Anda perhatikan, Anda dapat menentukan deskriptor file mana yang ingin Anda perpip dengan menggunakan tanda kurung setelah pipa.

Deskriptor file standar diberi nomor seperti itu:

  • 0: Input standar
  • 1: Output standar
  • 2: Kesalahan standar
Rolf
sumber
-3

Saya coba ikuti, ternyata berhasil juga,

command > /dev/null 2>&1 | grep 'something'
lasteye
sumber
Tidak bekerja Itu hanya mengirim stderr ke terminal. Abaikan pipa.
Tripp Kinetics