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?

jparanich
źródło
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"
lhunath
źródło
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
użytkownik542833
źródło
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}
Anthony
źródło
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
Josh Kelley
źródło
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.

Gilles „SO- przestań być zły”
źródło
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

ChristopheD
źródło
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.

haridsv
źródło
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.

Shuva
źródło
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 ).

Arminius
źródło