Jak napisać stderr do pliku, używając „tee” z potokiem?


541

Wiem, jak używać teedo zapisywania danych wyjściowych ( STDOUT) aaa.shdo bbb.out, jednocześnie wyświetlając je w terminalu:

./aaa.sh | tee bbb.out

Jak miałbym teraz pisać STDERRdo pliku o nazwie ccc.out, wciąż go wyświetlając?


2
Dla wyjaśnienia - czy chcesz, aby stderr poszedł do ekranu, a także do pliku?
— Charles Duffy

Zrobiłem to, edytuję swój post, aby to wyjaśnić. Wierzę, że rozwiązanie lhunatha wystarczy. Dzięki za pomoc wszystkim!
— jparanich

Odpowiedzi:


784

Zakładam, że nadal chcesz zobaczyć STDERR i STDOUT na terminalu. Możesz skorzystać z odpowiedzi Josha Kelleya, ale uważam, że trzymanie się tailw tle, które powoduje, że twój plik dziennika jest bardzo hackish i cludgy. Zauważ, jak musisz zachować eks FD i zrobić później porządek, zabijając go, a technicznie powinno to być zrobione w trap '...' EXIT.

Nie ma lepszego sposobu, aby to zrobić, a już odkrył go: tee.

Tylko, zamiast używać go tylko do standardowego wyjścia, przygotuj koszulkę dla standardowej i jedną dla standardowej. Jak to osiągniesz? Podstawianie procesów i przekierowywanie plików:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

Podzielmy to i wyjaśnijmy:

> >(..)

>(...)(podstawienie procesu) tworzy FIFO i pozwala teena nasłuchanie. Następnie używa >(przekierowanie pliku), aby przekierować STDOUT commanddo FIFO, którego teesłucha twój pierwszy .

To samo dotyczy drugiego:

2> >(tee -a stderr.log >&2)

Ponownie używamy podstawiania procesów, aby utworzyć teeproces, który odczytuje ze STDIN i wrzuca go do stderr.log. teewyprowadza swoje wejście z powrotem na STDOUT, ale ponieważ jego wejściem jest nasz STDERR, chcemy ponownie przekierować teeSTDOUT do naszego STDERR. Następnie używamy przekierowania pliku, aby przekierować commandSTDERR na wejście FIFO ( teeSTDIN).

Zobacz http://mywiki.wooledge.org/BashGuide/InputAndOutput

Zastępowanie procesów jest jedną z tych naprawdę uroczych rzeczy, które dostajesz jako bonus wyboru bashjako powłoki zamiast w przeciwieństwie do sh(POSIX lub Bourne).


W sh, trzeba robić rzeczy ręcznie:

out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"

5
Próbowałem tego: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)co działa, ale czeka na dane wejściowe. Czy istnieje prosty powód, dla którego tak się dzieje?
— Justin

1
@SillyFreak Nie rozumiem, co chcesz robić ani jaki masz problem. test echa; Wyjście nie generuje żadnego wyjścia na standardowym wyjściu, więc err pozostanie pusty.
— lhunath,

1
dzięki za ten komentarz; Zorientowałem się, jaki był potem mój błąd logiczny: po wywołaniu jako interaktywna powłoka, bash wypisuje wiersz polecenia i powtarza wyjście do stderr. Jednak jeśli stderr zostanie przekierowany, domyślnie bash zaczyna się jako nieinteraktywny; porównaj /bin/bash 2> erri/bin/bash -i 2> err
— Silly Freak

15
A dla tych, którzy „widzą, wierzą”, szybki test:(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
— Matthew Wilcoxson

2
Wow . Dobra odpowiedź: „
— Podzielmy

672

dlaczego nie po prostu:

./aaa.sh 2>&1 | tee -a log

To po prostu przekierowuje stderrdo stdout, więc tee echa zarówno do logowania, jak i do ekranu. Może coś mi brakuje, ponieważ niektóre inne rozwiązania wydają się bardzo skomplikowane.

Uwaga: Od wersji bash 4 możesz używać |&skrótu dla 2>&1 |:

./aaa.sh |& tee -a log

85
Działa to dobrze, jeśli chcesz, aby zarówno stdout (kanał 1), jak i stderr (kanał 2) były zalogowane do tego samego pliku (pojedynczy plik zawierający mieszaninę zarówno stdout, jak i sterr). Drugie, bardziej skomplikowane rozwiązanie pozwala rozdzielić stdout i stderr na 2 różne pliki (odpowiednio stdout.log i stderr.log). Czasami jest to ważne, czasem nie.
— Tyler Rick

19
Inne rozwiązania są w wielu przypadkach znacznie bardziej skomplikowane niż to konieczne. Ten działa dla mnie idealnie.
— dkaminy

13
Problem z tą metodą polega na tym, że tracisz kod wyjścia / statusu z procesu aaa.sh, co może być ważne (np. Podczas używania w pliku makefile). Nie masz tego problemu z zaakceptowaną odpowiedzią.
— Stefaan

9
jeśli nie masz nic przeciwko scaleniu stdout / stderr, to ./aaa.sh |& tee aaa.logdziała (w bash).
— jfs

5
@Stefaan Wierzę, że możesz zachować status wyjścia, jeśli poprzedzisz łańcuch poleceń, a set -o pipefailpo nim ;lub &&jeśli się nie mylę.
— David

59

Może to być przydatne dla osób, które znajdą to za pośrednictwem Google. Po prostu odkomentuj przykład, który chcesz wypróbować. Oczywiście możesz zmienić nazwę plików wyjściowych.

#!/bin/bash

STATUSFILE=x.out
LOGFILE=x.log

### All output to screen
### Do nothing, this is the default


### All Output to one file, nothing to the screen
#exec > ${LOGFILE} 2>&1


### All output to one file and all output to the screen
#exec > >(tee ${LOGFILE}) 2>&1


### All output to one file, STDOUT to the screen
#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)


### All output to one file, STDERR to the screen
### Note you need both of these lines for this to work
#exec 3>&1
#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)


### STDOUT to STATUSFILE, stderr to LOGFILE, nothing to the screen
#exec > ${STATUSFILE} 2>${LOGFILE}


### STDOUT to STATUSFILE, stderr to LOGFILE and all output to the screen
#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)


### STDOUT to STATUSFILE and screen, STDERR to LOGFILE
#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}


### STDOUT to STATUSFILE, STDERR to LOGFILE and screen
#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)


echo "This is a test"
ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff
ls -l ${0}

6
Nie, i sądzę, że exec może użyć jakiegoś wyjaśnienia. exec >oznacza, przenieś cel deskryptora pliku do określonego miejsca docelowego. Wartość domyślna to 1, więc exec > /dev/nullprzenosi wyjście stdout do / dev / null od teraz w tej sesji. Bieżące deskryptory plików dla tej sesji można zobaczyć wykonując ls -l /dev/fd/. Spróbuj! Następnie zobaczyć, co się dzieje, kiedy wydać exec 2>/tmp/stderr.log.Dodatkowo exec 3>&1środki, należy utworzyć nowy deskryptor pliku z numerem 3, i przekierować je do docelowego pliku deskryptora 1. Na przykład, cel był ekran, gdy polecenie zostało wydane.
— ogień

Przykład pokazujący stdout i stderr zarówno na ekranie, jak i osobnych plikach jest niesamowity !!! Wielkie dzięki!
— Marcello de Sales

21

Aby przekierować stderr do pliku, wyświetl stdout na ekranie, a także zapisz stdout do pliku:

./aaa.sh 2> ccc.out | tee ./bbb.out

EDYCJA : Aby wyświetlić stderr i stdout na ekranie, a także zapisać oba pliki, możesz użyć przekierowania We / Wy bash :

#!/bin/bash

# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out

# Also print the contents of this file to screen.
tail -f ccc.out &

# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out

# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1

1
Oczekuję, że użytkownik chce, aby stderr poszedł do swojej konsoli oprócz pliku, chociaż nie zostało to wyraźnie określone.
— Charles Duffy

2
Powinienem był być bardziej zrozumiały, chciałem też stderr na ekranie. Nadal podobało mi się rozwiązanie Josha Kelleya, ale uważam, że lhunath bardziej odpowiada moim potrzebom. Dzięki chłopaki!
— jparanich

18

Innymi słowy, chcesz połączyć stdout z jednym filtrem ( tee bbb.out), a stderr z innym filtrem ( tee ccc.out). Nie ma standardowego sposobu na podłączenie czegokolwiek innego niż standardowe wyjście do innego polecenia, ale można obejść ten problem, żonglując deskryptorami plików.

{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2

Zobacz także Jak grepować standardowy strumień błędów (stderr)? a kiedy użyjesz dodatkowego deskryptora pliku?

W bash (i ksh i zsh), ale nie w innych powłokach POSIX, takich jak dash, możesz użyć podstawiania procesów :

./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out)

Uważaj, że w bash to polecenie powraca natychmiast po ./aaa.shzakończeniu, nawet jeśli teepolecenia są nadal wykonywane (ksh i zsh czekają na podprocesy). Może to stanowić problem, jeśli robisz coś takiego ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. W takim przypadku użyj żonglowania deskryptorem pliku lub ksh / zsh.


4
Wydaje się, że to jedyna odpowiedź, która pozwala zachować strumienie stdout / stderr bez zmian (np. Nie łącząc ich). Słodkie!
— user1338062,

Najprostsze podejście sh, przydatne do zadań cron, w których podstawienie procesu nie jest dostępne.
— Roger Dueck

13

Jeśli używasz bash:

# Redirect standard out and standard error separately
% cmd >stdout-redirect 2>stderr-redirect

# Redirect standard error and out together
% cmd >stdout-redirect 2>&1

# Merge standard error with standard out and pipe
% cmd 2>&1 |cmd2

Kredyt (brak odpowiedzi z góry mojej głowy) idzie tutaj: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html


5

W moim przypadku skrypt wykonywał polecenie podczas przekierowywania zarówno stdout, jak i stderr do pliku, coś w stylu:

cmd > log 2>&1

Musiałem go zaktualizować tak, aby w przypadku awarii podjąć pewne działania w oparciu o komunikaty o błędach. Mógłbym oczywiście usunąć duplikat 2>&1i przechwycić stderr ze skryptu, ale wtedy komunikaty o błędach nie trafią do pliku dziennika w celach informacyjnych. Chociaż zaakceptowana odpowiedź @lhunath ma robić to samo, przekierowuje stdouti prowadzi stderrdo różnych plików, co nie jest tym, czego chcę, ale pomogło mi znaleźć dokładnie to, czego potrzebuję:

(cmd 2> >(tee /dev/stderr)) > log

W związku z powyższym dziennik będzie zawierał kopię obu stdouti stderrmogę przechwytywać stderrz mojego skryptu bez obaw stdout.


4

Poniższe będzie działać dla KornShell (ksh), gdzie podstawienie procesu nie jest dostępne,

# create a combined(stdin and stdout) collector
exec 3 <> combined.log

# stream stderr instead of stdout to tee, while draining all stdout to the collector
./aaa.sh 2>&1 1>&3 | tee -a stderr.log 1>&3

# cleanup collector
exec 3>&-

Prawdziwy podstęp tutaj jest sekwencja z 2>&1 1>&3którym w naszym przypadku przekierowuje stderrdo stdouti przekierowuje stdoutdo deskryptora 3. W tym momencie stderri stdoutnie zostały jeszcze połączone.

W efekcie stderr(as stdin) jest przekazywane do miejsca, w teektórym się loguje, stderr.loga także przekierowuje do deskryptora 3.

A deskryptor 3rejestruje to combined.logcały czas. Więc combined.logzawiera zarówno stdouti stderr.


Sprytne! Szkoda, że ​​jest niedoceniany, ponieważ to fajna opcja. Mam KSH, btw :)
— runlevel0

2

Jeśli używasz zsh , możesz użyć wielu przekierowań, więc nawet nie potrzebujesz tee:

./cmd 1>&1 2>&2 1>out_file 2>err_file

Tutaj po prostu przekierowujesz każdy strumień do siebie i do pliku docelowego.


Pełny przykład

% (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
out
err
% cat /tmp/out_file
out
% cat /tmp/err_file
err

Zauważ, że wymaga to ustawienia MULTIOSopcji (która jest domyślna).

MULTIOS

Wykonaj niejawne tees lub cats przy próbie wielokrotnego przekierowania (patrz Przekierowanie ).

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.