Jak mogę stderr potoku, a nie stdout?

981

Mam program, który zapisuje informacje do stdouti stderr, i muszę przejść grepprzez to, co nadchodzi do stderr , ignorując standardowe wyjście .

Oczywiście mogę to zrobić w 2 krokach:

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

ale wolałbym móc to zrobić bez plików tymczasowych. Czy są jakieś sprytne sztuczki do pipingu?

Peter Mortensen
źródło
Podobne pytanie, ale zachowując standardowe wyjście: unix.stackexchange.com/questions/3514/...
joeytwiddle
To pytanie dotyczyło Basha, ale warto wspomnieć o powiązanym artykule dotyczącym powłoki Bourne / Almquist.
Stephen Niedzielski
10
Spodziewałem się czegoś takiego: command 2| othercommand. Bash jest tak doskonały, że rozwój zakończył się w 1982 roku, więc obawiam się, że nigdy go nie zobaczymy.
Rolf

Odpowiedzi:

1188

Najpierw przekieruj stderr na stdout - potok; następnie przekieruj stdout na /dev/null(bez zmiany dokąd zmierza stderr):

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

Szczegółowe informacje na temat przekierowania we / wy w całej jego różnorodności znajdują się w rozdziale Przekierowania w podręczniku użytkownika Bash.

Należy zauważyć, że sekwencja przekierowań we / wy jest interpretowana od lewej do prawej, ale potoki są konfigurowane przed interpretacją przekierowań we / wy. Deskryptory plików, takie jak 1 i 2, są odniesieniami do otwartych opisów plików. Operacja 2>&1powoduje, że deskryptor pliku 2 aka stderr odnosi się do tego samego otwartego opisu pliku, co deskryptor pliku 1 aka stdout, do którego obecnie się odnosi (patrz dup2()i open()). Operacja >/dev/nullnastępnie zmienia deskryptor pliku 1, tak aby odwoływał się do otwartego opisu pliku /dev/null, ale nie zmienia to faktu, że deskryptor pliku 2 odnosi się do otwartego opisu pliku, do którego pierwotnie wskazywał deskryptor pliku 1 - mianowicie potok.

Jonathan Leffler
źródło
44
natknąłem się na / dev / stdout / dev / stderr / dev / stdin innego dnia i byłem ciekawy, czy to dobry sposób na zrobienie tego samego? Zawsze myślałem, że 2> i 1 było trochę zaciemnione. Więc coś w stylu: command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons,
17
Możesz użyć /dev/stdoutet al lub użyć /dev/fd/N. Będą nieznacznie mniej wydajne, chyba że powłoka traktuje je jako specjalne przypadki; czysta notacja numeryczna nie wymaga dostępu do plików według nazwy, ale korzystanie z urządzeń oznacza wyszukiwanie nazw plików. Czy można to zmierzyć, jest dyskusyjne. Podoba mi się zwięzłość notacji numerycznej - ale używam jej od tak dawna (ponad ćwierć wieku; ouch!), Że nie mam kwalifikacji, by oceniać jej zalety we współczesnym świecie.
Jonathan Leffler
23
@Jathanathan Leffler: Mam mały problem z wyjaśnieniem zwykłego tekstu „Przekieruj stderr na stdout, a następnie stdout na / dev / null” - Ponieważ trzeba czytać łańcuchy przekierowań od prawej do lewej (nie od lewej do prawej), powinien również dostosować nasze wyjaśnienie w postaci zwykłego tekstu do tego: „Przekieruj stdout na / dev / null, a następnie stderr tam, gdzie kiedyś było stdout” .
Kurt Pfeifle,
116
@KurtPfeifle: au contraire! Należy czytać łańcuchy przekierowań od lewej do prawej, ponieważ jest to sposób, w jaki powłoka przetwarza je. Pierwszą operacją jest 2>&1, co oznacza „podłącz stderr do deskryptora pliku, do którego obecnie zmierza stdout ”. Druga operacja to „zmień standard, więc idzie do /dev/null”, pozostawiając stderr idąc do pierwotnego standardu, czyli rury. Powłoka najpierw dzieli rzeczy na symbolu potoku, więc przekierowanie potoku następuje przed przekierowaniami 2>&1lub >/dev/null, ale to wszystko; pozostałe operacje są wykonywane od lewej do prawej. (Od prawej do lewej nie działa.)
Jonathan Leffler,
14
To, co mnie naprawdę zaskakuje, to to, że działa również w systemie Windows (po zmianie nazwy /dev/nullna odpowiednik Windows nul).
Michael Burr,
364

Lub aby zamienić dane wyjściowe ze standardowego błędu i standardowego wyjścia, użyj:

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

Spowoduje to utworzenie nowego deskryptora pliku (3) i przypisanie go do tego samego miejsca co 1 (standardowe wyjście), a następnie przypisanie fd 1 (standardowe wyjście) do tego samego miejsca co fd 2 (błąd standardowy) i na koniec przypisanie fd 2 (błąd standardowy ) w to samo miejsce, co fd 3 (wyjście standardowe).

Standardowy błąd jest teraz dostępny jako standardowe wyjście, a stare standardowe wyjście jest zachowywane jako standardowy błąd. Może to być przesada, ale mam nadzieję, że poda więcej szczegółów na temat deskryptorów plików Bash (dla każdego procesu dostępnych jest dziewięć).

Kramish
źródło
100
Ostatnią poprawką byłoby 3>&-zamknięcie zapasowego deskryptora, który stworzyłeś ze standardowego interfejsu
Jonathan Leffler
1
Czy możemy utworzyć deskryptor pliku, który ma stderri inny, który ma kombinację stderri stdout? Innymi słowy, czy można stderrprzejść jednocześnie do dwóch różnych plików?
Stuart
Poniższe nadal drukuje błędy na standardowe wyjście. czego mi brakuje? ls -l not_a_plik 3> i 1 1> i 2 2> i 3> błędy.txt
user48956
1
@ JonasDahlbæk: poprawianie jest przede wszystkim kwestią porządku. W naprawdę tajemniczych sytuacjach może to stanowić różnicę między procesem wykrywającym a niewykrywalnym EOF, ale wymaga to bardzo szczególnych okoliczności.
Jonathan Leffler,
1
Uwaga : zakłada się, że FD 3 nie jest już używany, nie zamyka go i nie cofa zamiany deskryptorów plików 1 i 2, więc nie można przejść dalej do kolejnej komendy. Zobacz tę odpowiedź, aby uzyskać więcej szczegółów i obejść. Aby uzyskać znacznie czystszą składnię dla {ba, z} sh, zobacz tę odpowiedź .
Tom Hale,
218

W Bash możesz również przekierowywać do podpowłoki za pomocą podstawiania procesów :

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

W rozpatrywanej sprawie:

command 2> >(grep 'something') >/dev/null
Rich Johnson
źródło
1
Działa bardzo dobrze w przypadku wyświetlania na ekranie. Czy masz pojęcie, dlaczego nieodparta zawartość pojawia się ponownie, jeśli przekierowuję wyjście grep do pliku? Po tym, jak command 2> >(grep 'something' > grep.log)grep.log zawiera te same dane wyjściowe, co ungrepped.log zcommand 2> ungrepped.log
Tim
9
Zastosowanie 2> >(stderr pipe >&2). W przeciwnym razie wyjście „rury stderr” przejdzie przez „rurkę stdlog”.
ceving
tak !, 2> >(...)działa, próbowałem, 2>&1 > >(...)ale nie udało się
datdinhquoc
Oto mały przykład, który może mi pomóc następnym razem, gdy sprawdzę, jak to zrobić. Rozważ następujące ... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) W tym przypadku chciałem również zobaczyć, co wychodzi jako błędy na mojej konsoli. Ale STDOUT szedł do pliku wyjściowego. Więc wewnątrz podpowłoki musisz przekierować STDOUT z powrotem do STDERR w nawiasach. Podczas gdy to działa, wyjście STDOUT teepolecenia kończy się na końcu out-content.txtpliku. To wydaje mi się niespójne.
będzie
@datdinhquoc Zrobiłem to jakoś2>&1 1> >(dest pipe)
Alireza Mohamadi,
195

Łącząc najlepsze z tych odpowiedzi, jeśli:

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

... wtedy wszystkie stdout są zachowywane jako stdout i wszystkie stderr są zachowywane jako stderr, ale nie zobaczysz żadnych linii w stderr zawierających ciąg „coś”.

Ma to tę wyjątkową zaletę, że nie odwraca ani nie odrzuca stdout i stderr, nie łączy ich razem ani nie używa plików tymczasowych.

Pinko
źródło
Czy to nie command 2> >(grep -v something)(bez 1>&2) to samo?
Francesc Rosas
11
Nie, bez tego filtrowany stderr ostatecznie zostaje skierowany na standardowe wyjście.
Pinko,
1
Właśnie tego potrzebowałem - plik wyjściowy tar zmienia się w miarę odczytu „dla katalogu”, więc zawsze chcę odfiltrować ten wiersz, ale sprawdzić, czy wystąpią inne błędy. Tak tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)powinno działać.
zdziwił się
błędnie jest mówić „zaczynając od ciągu”. W przedstawionej składni grep nie ma nic, co sprawi, że będzie on wykluczał tylko wiersze rozpoczynające się od podanego ciągu. Linie zostaną wykluczone, jeśli będą zawierały podany ciąg znaków w dowolnym miejscu.
Mike Nakis,
@MikeNakis dzięki - naprawiono! (To pozostało z mojego pierwotnego szkicu odpowiedzi, w którym miało to sens ...)
Pinko
102

O wiele łatwiej jest wizualizować rzeczy, jeśli myślisz o tym, co naprawdę dzieje się z „przekierowaniami” i „potokami”. Przekierowania i potoki w bash robią jedną rzecz: modyfikują tam, gdzie wskazują deskryptory plików procesowych 0, 1 i 2 (patrz / proc / [pid] / fd / *).

Gdy rura lub „|” operator jest obecny w wierszu poleceń, pierwszą rzeczą, która się wydarzy, jest to, że bash tworzy FIFO i wskazuje FD 1 komendy po lewej stronie na FO 0 i wskazuje FD 0 komendy po prawej stronie na tę samą FIFO.

Następnie operatory przekierowania dla każdej strony są oceniane od lewej do prawej , a bieżące ustawienia są używane za każdym razem, gdy występuje duplikacja deskryptora. Jest to ważne, ponieważ odkąd rura została skonfigurowana jako pierwsza, FD1 (lewa strona) i FD0 (prawa strona) zostały już zmienione w stosunku do tego, czym normalnie mogły być, a ich powielanie odzwierciedla ten fakt.

Dlatego po wpisaniu czegoś takiego:

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

Oto, co się dzieje, w kolejności:

  1. tworzony jest potok (fifo). „polecenie FD1” jest wskazane na tej rurze. „grep FD0” również wskazuje na tę rurę
  2. „polecenie FD2” jest wskazywane tam, gdzie „polecenie FD1” obecnie wskazuje (potok)
  3. „polecenie FD1” wskazuje na / dev / null

Zatem wszystkie dane wyjściowe, które „polecenie” zapisuje na FD 2 (stderr), trafiają do potoku i są odczytywane przez „grep” po drugiej stronie. Wszystkie dane wyjściowe, które „komenda” zapisuje na FD 1 (stdout), trafiają do / dev / null.

Jeśli zamiast tego uruchom następujące polecenie:

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

Oto co się dzieje:

  1. tworzona jest rura i są do niej wskazywane „polecenie FD 1” i „grep FD 0”
  2. „polecenie FD 1” wskazuje na / dev / null
  3. „polecenie FD 2” wskazuje miejsce, w którym obecnie wskazuje FD 1 (/ dev / null)

Zatem wszystkie stdout i stderr z „polecenia” idą do / dev / null. Nic nie trafia do potoku, dlatego „grep” zostanie zamknięty bez wyświetlania czegokolwiek na ekranie.

Zauważ też, że przekierowania (deskryptory plików) mogą być tylko do odczytu (<), tylko do zapisu (>) lub do odczytu-zapisu (<>).

Ostatnia uwaga. To, czy program napisze coś do FD1 lub FD2, zależy wyłącznie od programisty. Dobra praktyka programowania nakazuje, aby komunikaty o błędach trafiały do ​​FD 2, a normalne wyjście do FD 1, ale często można znaleźć niechlujne programowanie, które łączy oba lub w inny sposób ignoruje konwencję.

Michael Martinez
źródło
6
Naprawdę miła odpowiedź. Moją jedną sugestią byłoby zastąpienie pierwszego użycia „fifo” słowem „fifo (nazwana rura)”. Używam Linuksa od jakiegoś czasu, ale jakoś nigdy nie udało mi się dowiedzieć, że to kolejny termin nazwany potok. Uratowałoby mnie to od szukania tego, ale z drugiej strony nie nauczyłbym się innych rzeczy, które widziałem, kiedy się o tym dowiedziałem!
Mark Edington
3
@ MarkEdington Należy pamiętać, że FIFO jest tylko innym terminem dla nazwanego potoku w kontekście potoków i IPC . W bardziej ogólnym kontekście FIFO oznacza „pierwsze weszło, pierwsze wyszło”, które opisuje wstawianie i usuwanie ze struktury danych kolejki.
Loomchild
5
@Loomchild Oczywiście. Chodzi mi o to, że nawet jako doświadczony programista nigdy nie widziałem FIFO używanego jako synonim nazwanej fajki. Innymi słowy, nie wiedziałem tego: en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - Wyjaśnienie, że w odpowiedzi zaoszczędziłbym mi czasu.
Mark Edington
39

Jeśli używasz Bash, użyj:

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

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

Ken Sharp
źródło
4
Nie, |&równa się, 2>&1który łączy stdout i stderr. Pytanie wyraźnie zadawało dane wyjściowe bez standardowego wyjścia .
Profpatsch,
3
„Jeśli użyto„ | & ”, błąd standardowy polecenia 1 jest podłączony do standardowego wejścia polecenia 2 przez potok; jest skrótem dla 2> i 1 | ” Wykonano dosłownie z czwartego akapitu pod linkiem.
Profpatsch
9
@Profpatsch: Odpowiedź Kena jest poprawna, spójrz, że przekierowuje stdout na zero przed połączeniem stdout i stderr, więc dostaniesz do potoku tylko stderr, ponieważ stdout został wcześniej upuszczony do / dev / null.
Luciano,
3
Ale nadal stwierdziłem, że twoja odpowiedź jest nieprawidłowa, >/dev/null |&rozwinięcie do >/dev/null 2>&1 | i oznacza, że ​​stdout i-węzeł jest pusty do potoku, ponieważ nikt (# 1 # 2 oba powiązane z / dev / null i-węzeł) nie jest powiązany z stdout i-węzłem (np. ls -R /tmp/* >/dev/null 2>&1 | grep iDa puste, ale ls -R /tmp/* 2>&1 >/dev/null | grep ipozwoli # 2, który jest przywiązany do standardowego i-węzła, będzie potokował).
Fruit
3
Ken Sharp, przetestowałem i ( echo out; echo err >&2 ) >/dev/null |& grep "."nie daje wyników (tam, gdzie chcemy „błądzić”). man bashmówi Jeśli użyto | & ... jest skrótem dla 2> i 1 |. To niejawne przekierowanie standardowego błędu na standardowe wyjście jest wykonywane po wszelkich przekierowaniach określonych przez polecenie. Najpierw przekierowujemy FD1 polecenia na zero, a następnie przekierowujemy FD2 polecenia w miejsce wskazane przez FD1, tj. null, więc FD0 grepa nie otrzymuje danych wejściowych. Zobacz stackoverflow.com/a/18342079/69663 do wyjaśnienia bardziej dogłębne.
młot
11

Dla tych, którzy chcą przekierować stdout i stderr na stałe do plików, grep na stderr, ale zachowaj stdout do pisania wiadomości na 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
źródło
6

Spowoduje to przekierowanie komendy 1 stderr do komendy 2 stdin, pozostawiając komendę 1 stdout bez zmian.

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

Zaczerpnięte z LDP

theDolphin
źródło
2

Właśnie wymyśliłem rozwiązanie do wysyłania stdoutdo jednego polecenia i stderrdo drugiego, używając nazwanych potoków.

Tutaj idzie.

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

Prawdopodobnie dobrym pomysłem jest później usunięcie nazwanych rur.

Tripp Kinetics
źródło
0

Możesz użyć powłoki rc .

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

To przykład, w jaki sposób należy odrzucić standardowe wyjście i standardowy błąd potoku, aby grep w rc:

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

Możesz to zrobić bez wychodzenia z Bash:

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

Jak zapewne zauważyłeś, możesz określić, który deskryptor pliku ma być przesyłany potokowo, za pomocą nawiasów po potoku.

Standardowe deskryptory plików są ponumerowane jako takie:

  • 0: Standardowe wejście
  • 1: Wyjście standardowe
  • 2: Błąd standardowy
Rolf
źródło
-3

Staram się śledzić, ale to też działa,

command > /dev/null 2>&1 | grep 'something'
lasteye
źródło
Nie działa Po prostu wysyła stderr do terminala. Ignoruje fajkę.
Tripp Kinetics,