Bagaimana cara grep stream kesalahan standar (stderr)?

76

Saya menggunakan ffmpeg untuk mendapatkan info meta dari klip audio. Tapi saya tidak bisa menerimanya.

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    FFmpeg version SVN-r15261, Copyright (c) 2000-2008 Fabrice Bellard, et al.
      configuration: --prefix=/usr --bindir=/usr/bin 
      --datadir=/usr/share/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib
      --mandir=/usr/share/man --arch=i386 --extra-cflags=-O2 
      ...

Saya memeriksa, output ffmpeg ini diarahkan ke stderr.

$ ffmpeg -i 01-Daemon.mp3 2> /dev/null

Jadi saya pikir grep tidak dapat membaca aliran kesalahan untuk menangkap garis yang cocok. Bagaimana kita bisa mengaktifkan grep untuk membaca aliran kesalahan?

Menggunakan tautan nixCraft , saya mengarahkan aliran kesalahan standar ke aliran keluaran standar, lalu grep bekerja.

$ ffmpeg -i 01-Daemon.mp3 2>&1 | grep -i Duration
  Duration: 01:15:12.33, start: 0.000000, bitrate: 64 kb/s

Tetapi bagaimana jika kita tidak ingin mengarahkan stderr ke stdout?

Andrew-Dufresne
sumber
1
Saya percaya itu grephanya dapat beroperasi pada stdout (Meskipun saya tidak dapat menemukan sumber kanonik untuk mendukung itu), yang berarti bahwa setiap aliran perlu dikonversi ke stdout terlebih dahulu.
Stefan Lasiewski
9
@Stefan: grephanya bisa beroperasi di stdin. Ini adalah pipa yang dibuat oleh shell yang menghubungkan stdin grep ke stdout perintah lain. Dan shell hanya dapat menghubungkan stdout ke stdin.
Gilles
Aduh, kau benar. Saya pikir itu yang ingin saya katakan, saya hanya tidak memikirkannya. Terima kasih @Giles.
Stefan Lasiewski
Anda ingin tetap mencetak stdout?
Mikel

Jawaban:

52

Jika Anda menggunakan bashmengapa tidak menggunakan pipa anonim, intinya singkatan untuk apa yang dikatakan phunehehe:

ffmpeg -i 01-Daemon.mp3 2> >(grep -i Duration)

Jé Queue
sumber
3
+1 Bagus! Bash saja, tetapi jauh lebih bersih daripada alternatif.
Mikel
1
+1 Saya menggunakannya untuk memeriksa cpoutputcp -r dir* to 2> >(grep -v "svn")
Betlista
5
Jika Anda ingin output yang dialihkan diarahkan pada stderr lagi, tambahkan >&2, misalnyacommand 2> >(grep something >&2)
tlo
2
Tolong jelaskan bagaimana ini bekerja. 2>pengalihan stderr ke file, saya mengerti. Ini membaca dari file >(grep -i Duration). Tetapi file tidak pernah disimpan? Apa nama teknik ini sehingga saya bisa membaca lebih lanjut tentang itu?
Marko Avlijaš
1
Ini adalah "gula sintaksis" untuk membuat dan sebuah pipa (bukan file) dan ketika selesai menghapus pipa itu. Mereka secara efektif anonim karena mereka tidak diberi nama di sistem file. Bash menyebut proses ini substitusi.
Jé Queue
48

Tak satu pun dari kerang yang biasa (bahkan zsh) mengizinkan pipa selain dari stdout ke stdin. Tetapi semua shell Bourne-style mendukung penugasan kembali deskriptor file (seperti pada 1>&2). Jadi, Anda dapat sementara mengalihkan stdout ke fd 3 dan stderr ke stdout, dan kemudian mengembalikan fd 3 ke stdout. Jika stuffmenghasilkan beberapa output pada stdout dan beberapa output pada stderr, dan Anda ingin menerapkan filterpada output kesalahan membiarkan output standar tidak tersentuh, Anda dapat menggunakan { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1.

$ stuff () {
  echo standard output
  echo more output
  echo standard error 1>&2
  echo more error 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
standard output
more output
standard error
Gilles
sumber
Pendekatan ini berhasil. Sayangnya, dalam kasus saya, jika nilai pengembalian non-nol dikembalikan, ia akan hilang - nilai yang dikembalikan adalah 0 untuk saya. Ini mungkin tidak selalu terjadi, tetapi itu terjadi dalam kasus yang sedang saya perhatikan. Apakah ada cara untuk menyimpannya?
Faheem Mitha
2
@FaheemMitha Tidak yakin apa yang Anda lakukan, tapi mungkin pipestatusakan membantu
Gilles
1
@FaheemMitha, juga set -o pipefaildapat membantu di sini, tergantung pada apa yang ingin Anda lakukan dengan status kesalahan. (Misalnya, jika Anda telah set -emengaktifkan kegagalan pada kesalahan apa pun, Anda mungkin set -o pipefailjuga menginginkannya .)
Wildcard
@ Kartu Memori Ya, saya telah set -emengaktifkan gagal pada kesalahan apa pun.
Faheem Mitha
The rcshell memungkinkan pipa stderr. Lihat jawaban saya di bawah ini.
Rolf
20

Ini mirip dengan "file temp trik" phunehehe, tetapi menggunakan pipa bernama sebagai gantinya, memungkinkan Anda untuk mendapatkan hasil yang sedikit lebih dekat dengan ketika mereka dihasilkan, yang dapat berguna untuk perintah yang berjalan lama:

$ mkfifo mypipe
$ command 2> mypipe | grep "pattern" mypipe

Dalam konstruksi ini, stderr akan diarahkan ke pipa bernama "mypipe". Karena greptelah dipanggil dengan argumen file, ia tidak akan meminta input dari STDIN. Sayangnya, Anda masih harus membersihkan pipa bernama itu setelah Anda selesai.

Jika Anda menggunakan Bash 4, ada sintaks pintasan untuk command1 2>&1 | command2, yaitu command1 |& command2. Namun, saya percaya bahwa ini murni cara pintas sintaksis, Anda masih mengarahkan ulang STDERR ke STDOUT.

Steven D
sumber
Terkait: stackoverflow.com/questions/2871233/…
Stefan Lasiewski
1
Itu |&sintaks yang sempurna untuk membersihkan Ruby jejak stderr tumpukan kembung. Saya akhirnya bisa menangkap mereka tanpa terlalu banyak kesulitan.
pgr
8

Jawaban Gilles dan Stefan Lasiewski sama-sama baik, tetapi cara ini lebih sederhana:

ffmpeg -i 01-Daemon.mp3 2>&1 >/dev/null | grep "pattern"

Saya berasumsi Anda tidak ingin ffmpeg'sstdout dicetak.

Bagaimana itu bekerja:

  • pipa dulu
    • ffmpeg dan grep dimulai, dengan stfout ffmpeg akan menjadi stdin grep
  • pengalihan berikutnya, kiri ke kanan
    • stderr ffmpeg diatur ke apa pun stdout-nya (saat ini pipa)
    • stdout ffmpeg diatur ke / dev / null
Mikel
sumber
Deskripsi ini membingungkan saya. Dua peluru terakhir membuat saya berpikir "redirect stderr ke stdout", lalu "redirect stdout (dengan stderr, sekarang) ke / dev / null". Namun, ini bukan apa yang sebenarnya dilakukan. Pernyataan-pernyataan itu tampaknya dibalik.
Steve
1
@ Sete Tidak ada "dengan stderr" di peluru kedua. Pernahkah Anda melihat unix.stackexchange.com/questions/37660/order-of-redirections ?
Mikel
Tidak, maksud saya itu interpretasi saya tentang bagaimana Anda menggambarkannya dalam bahasa Inggris. Tautan yang Anda berikan sangat berguna.
Steve
Mengapa itu perlu diarahkan ke / dev / null?
Kanwaljeet Singh
7

Lihat di bawah untuk skrip yang digunakan dalam tes ini.

Grep hanya dapat beroperasi pada stdin, jadi karena itu Anda harus mengonversi aliran stderr dalam bentuk yang dapat diurai Grep.

Biasanya, stdout dan stderr keduanya dicetak ke layar Anda:

$ ./stdout-stderr.sh
./stdout-stderr.sh: Printing to stdout
./stdout-stderr.sh: Printing to stderr

Untuk menyembunyikan stdout, tetapi tetap cetak stderr lakukan ini:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Printing to stderr

Tetapi grep tidak akan beroperasi pada stderr! Anda akan mengharapkan perintah berikut untuk menekan baris yang mengandung 'err', tetapi tidak.

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Printing to stderr

Inilah solusinya.

Sintaks Bash berikut akan menyembunyikan output ke stdout, tetapi masih akan menampilkan stderr. Pertama kita mem-pipe stdout ke / dev / null, lalu kita mengonversi stderr ke stdout, karena pipa Unix hanya akan beroperasi di stdout. Anda masih dapat menerima teks.

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Printing to stderr

(Perhatikan bahwa perintah di atas berbeda dengan itu ./command >/dev/null 2>&1, yang merupakan perintah yang sangat umum).

Inilah skrip yang digunakan untuk pengujian. Ini mencetak satu baris ke stdout dan satu baris ke stderr:

#!/bin/sh

# Print a message to stdout
echo "$0: Printing to stdout"
# Print a message to stderr
echo "$0: Printing to stderr" >&2

exit 0
Stefan Lasiewski
sumber
Jika Anda mengalihkan pengalihan, Anda tidak perlu semua kawat gigi. Lakukan saja ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel
3

Ketika Anda menyalurkan output dari satu perintah ke perintah lainnya (menggunakan |), Anda hanya mengarahkan keluaran standar. Jadi itu harus menjelaskan alasannya

ffmpeg -i 01-Daemon.mp3 | grep -i Duration

tidak menampilkan apa yang Anda inginkan (itu berhasil, meskipun).

Jika Anda tidak ingin mengarahkan output kesalahan ke output standar, Anda dapat mengarahkan output kesalahan ke file, lalu ambil nanti

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error
phhehehe
sumber
Terima kasih. it does work, thoughAnda maksud itu berfungsi pada mesin Anda? Kedua, seperti yang Anda tunjukkan menggunakan pipa, kita hanya bisa mengarahkan ulang stdout. Saya tertarik pada beberapa fitur perintah atau bash yang akan membiarkan saya mengarahkan stderr. (tapi bukan trik temp file)
Andrew-Dufresne
@Andrew Maksudku, perintahnya bekerja dengan cara yang telah dirancang untuk bekerja. Itu tidak bekerja seperti yang Anda inginkan :)
phunehehe
Saya tidak tahu cara apa pun yang dapat mengarahkan kesalahan output dari perintah ke input standar yang lain. Akan menarik jika seseorang bisa menunjukkan itu.
phunehehe
2

Anda dapat menukar streaming. Ini akan memungkinkan Anda ke grepstream kesalahan standar asli sambil tetap mendapatkan output yang awalnya pergi ke output standar di terminal:

somecommand 3>&2 2>&1 1>&3- | grep 'pattern'

Ini bekerja dengan terlebih dahulu membuat file descriptor baru (3) terbuka untuk output dan mengaturnya ke aliran kesalahan standar ( 3>&2). Kemudian kami mengarahkan kesalahan standar ke output standar ( 2>&1). Akhirnya keluaran standar dialihkan ke kesalahan standar asli, dan deskriptor file baru ditutup ( 1>&3-).

Dalam kasus Anda:

ffmpeg -i 01-Daemon.mp3 3>&2 2>&1 1>&3- | grep -i Duration

Mengujinya:

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep "error"
output
error

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep -v "error"
output
Kusalananda
sumber
1

Saya suka menggunakan rcshell dalam hal ini.

Pertama instal paket (kurang dari 1MB).

Ini adalah contoh bagaimana Anda akan membuang stdoutdan memasang pipa stderruntuk menerima 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, sintaksinya langsung, yang menjadikan ini solusi pilihan saya.

Anda bisa menentukan deskriptor file mana yang ingin Anda perpipkan di dalam tanda kurung, tepat setelah karakter pipa.

Deskriptor file standar diberi nomor seperti itu:

  • 0: Input standar
  • 1: Ouptut standar
  • 2: Kesalahan standar
Rolf
sumber
0

Variasi pada contoh sub proses bash:

gema dua baris ke stderr dan tee stderr ke file, ambil tee dan pipa kembali ke stdout

(>&2 echo -e 'asdf\nfff\n') 2> >(tee some.load.errors | grep 'fff' >&1)

stdout:

fff

some.load.errors (misalnya stderr):

asdf
fff
Jonunsch
sumber
-1

coba perintah ini

  • buat file dengan nama acak
  • kirim output ke file
  • mengirim konten file ke pipa
  • grep

ceva -> hanya nama variabel (bahasa Inggris = sesuatu)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;
Rodislav Moldovan
sumber
Saya akan menggunakan /tmp/$ceva, lebih baik daripada mengotori direktori saat ini dengan file sementara - dan Anda mengasumsikan bahwa direktori saat ini dapat ditulis.
Rolf