Standart hata akışı (stderr) nasıl grep edilir?


76

Bir ses klibinin meta bilgilerini almak için ffmpeg kullanıyorum. Ama ben onu ezemiyorum.

    $ 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 
      ...

Kontrol ettim, bu ffmpeg çıkışı stderr'ye yönlendirildi.

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

Bu nedenle grep'in eşleşen satırları yakalamak için hata akışını okuyamadığını düşünüyorum. Grep'in hata akışını okumasını nasıl sağlayabiliriz?

NixCraft linkini kullanarak standart hata akışını standart çıkış akışına yönlendirdim, sonra grep çalıştı.

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

Fakat ya stderr'i stdout'a yönlendirmek istemezsek ne olur?


1
Bunun grepyalnızca stdout'ta çalışabileceğine inanıyorum (Bunu desteklemek için kanonik kaynağı bulamıyorum, ancak bu, herhangi bir akışın ilk önce stdout'a dönüştürülmesi gerektiği anlamına geliyor.
Stefan Lasiewski

9
@Stefan: grepsadece stdin'de çalışabilir. Grep'in stdinini diğer komutun stdout'una bağlayan kabuk tarafından oluşturulan boru. Ve kabuk sadece bir stdout'u bir stdin'e bağlayabilir.
Gilles

Hata! Haklısın. Sanırım gerçekten demek istediğim buydu, sadece düşünmedim. @Giles'e teşekkürler.
Stefan Lasiewski

Hala stdout yazdırmasını istiyor musun?
Mikel

Yanıtlar:


52

bashNeden isimsiz borular kullanmıyorsanız , özünde söylediklerine göre özünde:

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


3
+1 Güzel! Sadece Bash, ama alternatiflerden çok daha temiz.
Mikel

1
+1 cpÇıktıyı kontrol etmek için kullandımcp -r dir* to 2> >(grep -v "svn")
Betlista

5
Tekrar standart hataya süzülmüş yeniden yönlendirilmiş çıktı istiyorsanız, bir ekleme >&2, örneğincommand 2> >(grep something >&2)
TLO

2
Lütfen bunun nasıl çalıştığını açıklayın. 2>stderr'i dosyaya yönlendirir, anlıyorum. Bu dosyadan okur >(grep -i Duration). Ancak dosya asla saklanmaz? Bu teknik ne denir, bu yüzden daha fazla okuyabilirim?
Marko Avlijaš

1
Bir boru ("dosya değil") oluşturmak ve bir boru oluşturmak "tamamlayıcı şeker" dir ve tamamlandığında bu boruyu çıkarın. Etkin bir şekilde isimsizdirler çünkü dosya sisteminde bir isim verilmezler. Bash bu işlem değişikliğini çağırıyor.
Jé Queue

48

Her zamanki mermilerin hiçbiri (zsh bile) stdout'tan stdin'den başka borulara izin vermez. Ancak tüm Bourne tarzı mermiler, dosya tanımlayıcısının yeniden atanmasını (olduğu gibi 1>&2) destekler. Böylece stdout'u geçici olarak fd 3'e, stderr'i stdout'a yönlendirebilir ve daha sonra fd 3'ü tekrar stdout'a geri döndürebilirsiniz. Eğer stuffstdout'ta bazı çıktı ve standart hataya bazı çıktı üretir ve uygulamak istediğiniz filterstandart çıktı dokunmamak hata çıkışı üzerine, kullanabilirsiniz { 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

Bu yaklaşım işe yarıyor. Ne yazık ki, benim durumumda, sıfır olmayan bir geri dönüş değeri döndürülürse kaybedilir - döndürülen değer benim için 0'dır. Bu her zaman olmayabilir, ancak şu anda bakmam durumunda olabilir. Onu kurtarmanın bir yolu var mı?
Faheem Mitha

2
@FaheemMitha Ne yaptığınızdan emin değilsiniz, ama belki pipestatusyardımcı olur
Gilles

1
@FaheemMitha, set -o pipefailhata durumuyla ne yapmak istediğinize bağlı olarak, burada da yardımcı olabilir. (Örneğin set -e, herhangi bir hatayı başarısızlığa uğrattıysanız, muhtemelen de istersiniz set -o pipefail.)
Wildcard

@Wildcard Evet, set -eherhangi bir hatayla ilgili başarısızlığa uğradım .
Faheem Mitha

rcKabuk boru stderr sağlar. Bkz cevabımı altında.
Rolf,

20

Bu, phunehehe'nin "temp file hile" sine benzer, ancak bunun yerine adlandırılmış bir boru kullanır, sonuçları çıktığında biraz daha yaklaştırabilmenizi sağlar; bu, uzun süren komutlar için kullanışlıdır:

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

Bu yapımda stderr "mypipe" adlı boruya yönlendirilecektir. Yana grepbir dosya argümanı ile anılmış, onun girişi için STDIN bakmak olmaz. Ne yazık ki, işiniz bittiğinde söz konusu boruyu temizlemeniz gerekecek.

Eğer Bash 4 kullanıyorsanız, bir kısayol sözdizimi vardır command1 2>&1 | command2vardır command1 |& command2. Ancak, bunun tamamen bir sözdizimi kısayolu olduğuna inanıyorum, hala STDERR'i STDOUT'a yönlendiriyorsunuz.



1
Bu |&sözdizimi şişirilmiş Ruby stderr yığın izlerini temizlemek için mükemmeldir. Sonunda çok fazla güçlük çekmeden bunları yakalayabilirim.
pgr

8

Gilles ve Stefan Lasiewski'nin cevaplarının ikisi de iyidir, ancak bu yol daha basittir:

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

ffmpeg'sStdout'un basılmasını istemediğini farz ediyorum .

Nasıl çalışır:

  • ilk önce borular
    • ffmpeg ve grep başlatılır, ffmpeg'in stdout'u grep'in stdinine gider
  • sonraki yönlendirmeler, soldan sağa
    • ffmpeg's stderr değeri stdout değeri ne olursa olsun (şu anda boru)
    • ffmpeg'ın stdout işlevi / dev / null olarak ayarlandı

Bu açıklama benim için kafa karıştırıcı. Son iki madde işareti, "stderr'i stdout'a yönlendir", sonra da "stdout'u (şimdi stderr ile) / dev / null" yönlendiriyor. Ancak, bunun gerçekten yaptığı şey bu değil. Bu ifadeler tersine çevrilmiş görünüyor.
Steve

1
@ Steve İkinci mermide "stderr" yok. Unix.stackexchange.com/questions/37660/order-of-redirections adresini gördün ?
Mikel

Hayır, bunu demek benim İngilizce olarak niteleyen nasıl yorumlanması. Sağladığınız bağlantı yine de çok yararlı.
Steve

Neden / dev / null'a yönlendirilmesi gerekiyordu?
Kanwaljeet Singh

7

Bu testlerde kullanılan senaryo için aşağıya bakınız.

Grep yalnızca stdin'de çalışabilir, bu nedenle stderr akışını Grep'in ayrıştırabileceği bir biçimde dönüştürmeniz gerekir.

Normalde, stdout ve stderr ekranınıza yazdırılır:

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

Stdout'u gizlemek, ancak yine de stderr yazdırmak için şunu yapın:

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

Fakat grep stderr'de işlemez! Aşağıdaki komutu 'err' içeren satırları gizlemesini beklersiniz, ancak yapmaz.

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

İşte çözüm.

Aşağıdaki Bash sözdizimi çıktıyı stdout'a gizler ancak yine de stderr gösterecektir. Önce stdout'u / dev / null olarak değiştiririz, daha sonra stderr'i stdout'a çeviririz, çünkü Unix boruları sadece stdout'ta çalışır. Yine de metni okuyabilirsiniz.

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

(Bu komut olduğuna dikkat edin , farklı daha sonra ./command >/dev/null 2>&1çok yaygın bir komut olan).

İşte test için kullanılan komut dosyası. Bu bir satırı stdout'a ve bir satırı stderr'e yazdırır:

#!/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

Yönlendirmeleri değiştirirseniz, tüm desteklere ihtiyacınız yoktur. Sadece yap ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel

3

Bir komutun çıktısını diğerine (kullanarak |) aktardığınızda, yalnızca standart çıktısını yönlendirirsiniz. Bu yüzden nedenini açıklamalıdır

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

istediğini çıkarmaz (yine de çalışır).

Hata çıkışını standart çıkışa yönlendirmek istemiyorsanız, hata çıkışını bir dosyaya yönlendirebilirsiniz, daha sonra grep

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error

Teşekkürler. it does work, though, makinen üzerinde çalışıyor mu demek istiyorsun? İkincisi, boru kullanarak belirttiğiniz gibi sadece stdout yönlendirebiliriz. Stderr komutunu yönlendirmeme izin verecek bazı komut veya bash özellikleriyle ilgileniyorum. (ancak geçici dosya numarası değil)
Andrew-Dufresne

@Andrew, komut çalışması için tasarlanmış şekilde çalışır. Sadece istediğiniz şekilde çalışmıyor :)
phunehehe

Bir komutun hata çıktısını bir başkasının standart girişine yönlendirebilen herhangi bir yol bilmiyorum. Biri bunu gösterebilirse ilginç olurdu.
phunehehe

2

Akışları değiştirebilirsin. Bu grep, başlangıçta terminalde standart çıkışa giden çıkışı elde ederken orijinal standart hata akışına izin verir:

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

Bu, ilk önce çıktı için açık olan yeni bir dosya tanımlayıcı (3) oluşturarak ve onu standart hata akışına ( 3>&2) ayarlayarak çalışır . Sonra standart hatayı standart çıktıya ( 2>&1) yönlendiririz . Son olarak standart çıktı orijinal standart hataya yönlendirilir ve yeni dosya tanımlayıcısı kapanır ( 1>&3-).

Senin durumunda:

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

Test etmek:

$ ( 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

1

rcBu durumda kabuğu kullanmayı seviyorum .

İlk önce paketi kurun (1 MB'den küçük).

Bu, nasıl atılacağınıza stdoutve stderraşılayacağınız bir boruya örnektir rc:

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

Bash'den ayrılmadan yapabilirsiniz:

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

Fark etmiş olabileceğiniz gibi, sözdizimi oldukça basittir, bu da benim tercih ettiğim çözümdür.

Pipo karakterinin hemen ardından, hangi dosya tanımlayıcısını desteklerin içine koyulacağını belirtebilirsiniz.

Standart dosya tanımlayıcıları şöyle numaralandırılmıştır:

  • 0: Standart giriş
  • 1: Standart çıkış
  • 2: Standart hata

0

Bash alt işlem örneğinde bir varyasyon:

stderr ve tee stderr için bir dosyaya iki satır eko, tee grep ve geri stdout dışarı boru

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

stdout'u:

fff

some.load.errors (örneğin, stderr):

asdf
fff

-1

bu komutu dene

  • rastgele isimle dosya yarat
  • dosyaya çıktı gönder
  • dosyanın içeriğini boruya gönder
  • grep

ceva -> sadece değişken bir isim (ingilizce = birşey)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;

/tmp/$cevaGeçerli dizini geçici dosyalarla doldurmaktan daha iyi kullanırdım - ve bu dizinin yazılabilir olduğunu varsayıyorsunuz.
Rolf,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.