Jak grep standardowy strumień błędów (stderr)?


76

Korzystam z ffmpeg, aby uzyskać meta informacje o klipie audio. Ale nie jestem w stanie tego zepsuć.

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

Sprawdziłem, to wyjście ffmpeg jest kierowane do stderr.

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

Myślę więc, że grep nie może odczytać strumienia błędów, aby złapać pasujące linie. Jak możemy włączyć grep do odczytu strumienia błędów?

Za pomocą linku nixCraft przekierowałem standardowy strumień błędów do standardowego strumienia wyjściowego, a następnie grep zadziałał.

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

Ale co, jeśli nie chcemy przekierowywać stderr na stdout?


1
Wierzę, że grepmoże działać tylko na standardowym wyjściu (chociaż nie mogę znaleźć źródła kanonicznego, które by to poparło), co oznacza, że ​​każdy strumień musi zostać najpierw przekonwertowany na standardowe wyjście.
Stefan Lasiewski

9
@Stefan: grepmoże działać tylko na standardowym wejściu . Jest to potok utworzony przez powłokę, który łączy stdin grep z stdout drugiego polecenia. A powłoka może tylko podłączyć stdout do stdin.
Gilles

Ups, masz rację. Myślę, że tak naprawdę chciałem powiedzieć, po prostu tego nie przemyślałem. Dzięki @Giles.
Stefan Lasiewski

Czy chcesz, aby nadal wyświetlał standardowe wyjście?
Mikel

Odpowiedzi:


52

Jeśli używasz, bashdlaczego nie zastosujesz anonimowych potoków, w skrócie stwórz to, co powiedział phunehe:

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


3
+1 fajnie! Bash tylko, ale o wiele czystszy niż alternatywy.
Mikel

1
+1 Użyłem tego do sprawdzenia cpwynikówcp -r dir* to 2> >(grep -v "svn")
Betlista

5
Jeśli chcesz ponownie filtrować przekierowane wyjście na stderr, dodaj >&2np.command 2> >(grep something >&2)
tlo

2
Proszę wyjaśnić, jak to działa. 2>przekierowuje stderr do pliku, rozumiem. To czyta z pliku >(grep -i Duration). Ale plik nigdy nie jest przechowywany? Jak nazywa się ta technika, abym mógł przeczytać więcej na jej temat?
Marko Avlijaš

1
Jest to „cukier składniowy”, który należy utworzyć i potok (nie plik), a po zakończeniu usuń potok. Są skutecznie anonimowi, ponieważ nie mają nazwy w systemie plików. Bash nazywa ten proces podstawieniem.
Jé Queue,

48

Żadna ze zwykłych powłok (nawet zsh) nie zezwala na rury inne niż od standardowego do standardowego. Ale wszystkie powłoki w stylu Bourne'a obsługują zmianę przypisania deskryptorów plików (jak w 1>&2). Możesz więc tymczasowo przekierować stdout na fd 3 i stderr na stdout, a później przywrócić fd 3 na stdout. Jeśli stuffprodukuje jakieś wyjście na standardowym wyjściu, a niektóre na standardowym wyjściu, a chcesz zastosować filterna wyjściu błędu, pozostawiając standardowe wyjście nietknięte, możesz użyć { 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

To podejście działa. Niestety, w moim przypadku, jeśli zwrócona zostanie niezerowa wartość zwrotna, zostanie utracona - dla mnie zwrócona wartość wynosi 0. To nie zawsze może się zdarzyć, ale dzieje się tak w przypadku, na który patrzę obecnie. Czy jest jakiś sposób, aby go zapisać?
Faheem Mitha

2
@FaheemMitha Nie jestem pewien, co robisz, ale może pipestatuspomoże
Gilles

1
@FaheemMitha, set -o pipefailmoże być również pomocny tutaj, w zależności od tego, co chcesz zrobić ze statusem błędu. (Na przykład, jeśli set -ewłączyłeś się, aby nie powieść się na jakiekolwiek błędy, prawdopodobnie set -o pipefailteż chcesz ).
Wildcard

@Wildcard Tak, set -ewłączyłem się, aby nie powieść żadnych błędów.
Faheem Mitha

rcPowłoka umożliwia rurowe sys.stderr. Zobacz moją odpowiedź poniżej.
Rolf

20

Jest to podobne do „sztuczki z plikami tymczasowymi” phunehehe, ale zamiast tego używa nazwanego potoku, co pozwala uzyskać wyniki nieco bliżej, gdy są one wypisywane, co może być przydatne w przypadku długotrwałych poleceń:

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

W tej konstrukcji stderr zostanie skierowany do rury o nazwie „mypipe”. Ponieważ grepzostał wywołany z argumentem pliku, nie będzie szukał STDIN pod kątem danych wejściowych. Niestety, po zakończeniu będziesz musiał wyczyścić nazwaną rurę.

Jeśli używasz Bash 4, istnieje składnia skrótu command1 2>&1 | command2, czyli command1 |& command2. Uważam jednak, że jest to wyłącznie skrót składniowy, nadal przekierowujesz STDERR do STDOUT.



1
Ta |&składnia jest idealna do usuwania rozdętych śladów stosu Ruby stderr. Mogę w końcu grepować bez większych problemów.
pgr

8

Odpowiedzi Gillesa i Stefana Lasiewskiego są dobre, ale ten sposób jest prostszy:

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

Zakładam, że nie chcesz ffmpeg'sdrukować standardowego.

Jak to działa:

  • najpierw rury
    • ffmpeg i grep są uruchamiane, a stfout ffmpeg przechodzi do stdin grep
  • przekierowania dalej, od lewej do prawej
    • Stderr ffmpeg jest ustawiony na cokolwiek jego standardowe wyjście (obecnie potok)
    • Standardowe wyjście ffmpeg jest ustawione na / dev / null

Ten opis jest dla mnie mylący. Ostatnie dwa pociski powodują, że myślę „przekieruj stderr na stdout”, a następnie „przekieruj stdout (teraz ze stderr) do / dev / null”. Jednak tak naprawdę nie działa. Te stwierdzenia wydają się odwrócone.
Steve

1
@ Steve Nie ma słowa „z stderr” w drugim punkcie. Czy widziałeś unix.stackexchange.com/questions/37660/order-of-redirections ?
Mikel

Nie, to znaczy moja interpretacja tego, jak opisałeś to po angielsku. Podany link jest jednak bardzo przydatny.
Steve,

Dlaczego konieczne było przekierowanie do / dev / null?
Kanwaljeet Singh

7

Zobacz poniżej skrypt używany w tych testach.

Grep może działać tylko na stdin, dlatego musisz przekonwertować strumień stderr w postać, którą Grep może analizować.

Zwykle stdout i stderr są drukowane na ekranie:

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

Aby ukryć stdout, ale nadal drukować stderr, wykonaj następujące czynności:

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

Ale grep nie będzie działał na stderr! Oczekuje się, że następujące polecenie ukryje wiersze zawierające „err”, ale tak nie jest.

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

Oto rozwiązanie.

Poniższa składnia Bash ukryje dane wyjściowe na stdout, ale nadal pokaże stderr. Najpierw przesyłamy stdout do / dev / null, a następnie konwertujemy stderr na stdout, ponieważ rury uniksowe będą działać tylko na stdout. Nadal możesz grepować tekst.

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

(Należy zauważyć, że powyższe polecenia jest inny czym ./command >/dev/null 2>&1, co jest bardzo powszechne polecenia).

Oto skrypt używany do testowania. Spowoduje to wydrukowanie jednej linii na standardowe wyjście i jednej linii na standardowe wyjście:

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

Jeśli zmienisz przekierowania, nie potrzebujesz wszystkich nawiasów klamrowych. Po prostu zrób ./stdout-stderr.sh 2>&1 >/dev/null | grep err.
Mikel

3

Kiedy potokujesz wyjście jednego polecenia do drugiego (używając |), przekierowujesz tylko standardowe wyjście. To powinno wyjaśniać dlaczego

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

nie wyświetla tego, co chciałeś (ale działa).

Jeśli nie chcesz przekierowywać wyjścia błędu na standardowe wyjście, możesz przekierować wyjście błędu do pliku, a następnie grep go później

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

Dzięki. it does work, though, masz na myśli, że działa na twoim komputerze? Po drugie, jak zauważyłeś za pomocą potoku, możemy jedynie przekierować standardowe wyjście. Interesuje mnie funkcja polecenia lub bash, która pozwoli mi przekierować stderr. (ale nie sztuczka z plikiem tymczasowym)
Andrew-Dufresne,

@Andrew Mam na myśli, że polecenie działa tak, jak zostało zaprojektowane do działania. To po prostu nie działa tak, jak chcesz :)
phunehehe

Nie znam żadnego sposobu, który mógłby przekierować wyjście błędu do standardowego wejścia innego polecenia. Byłoby interesujące, gdyby ktoś mógł to wskazać.
phunehehe,

2

Możesz zamienić strumienie. Umożliwiłoby to przejście do greporyginalnego standardowego strumienia błędów, a jednocześnie uzyskanie danych wyjściowych, które pierwotnie trafiły do ​​standardowego wyjścia w terminalu:

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

Działa to najpierw poprzez utworzenie nowego deskryptora pliku (3) otwartego dla wyjścia i ustawienie go na standardowy strumień błędów ( 3>&2). Następnie przekierowujemy błąd standardowy do standardowego wyjścia ( 2>&1). Wreszcie standardowe wyjście jest przekierowywane do pierwotnego standardowego błędu, a nowy deskryptor pliku jest zamknięty ( 1>&3-).

W Twoim przypadku:

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

Testowanie:

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

rcW tym przypadku lubię używać powłoki.

Najpierw zainstaluj pakiet (jest mniej niż 1 MB).

Oto przykład, w jaki sposób odrzucisz stdouti potokujesz, stderraby grepować rc:

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

Możesz to zrobić bez opuszczania Bash:

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

Jak zapewne zauważyłeś, składnia jest prosta, co czyni to moje preferowane rozwiązanie.

Możesz określić, który deskryptor pliku ma być umieszczony w nawiasach, tuż po znaku potoku.

Standardowe deskryptory plików są ponumerowane jako takie:

  • 0: Standardowe wejście
  • 1: Standardowy ouptut
  • 2: Błąd standardowy

0

Odmiana przykładu podprocesu bash:

echo dwie linie do stderr i tee stderr do pliku, grep tee i potok z powrotem na standardowe wyjście

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

standardowe:

fff

some.load.errors (np. stderr):

asdf
fff

-1

spróbuj tego polecenia

  • utwórz plik o losowej nazwie
  • wyślij dane wyjściowe do pliku
  • wyślij zawartość pliku do potoku
  • grep

ceva -> tylko nazwa zmiennej (angielski = coś)

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

Chciałbym użyć /tmp/$ceva, lepiej niż zaśmiecanie bieżącego katalogu plikami tymczasowymi - i zakładasz, że bieżący katalog jest zapisywalny.
Rolf
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.