Jak zapisać stderr do pliku podczas używania" tee " z rurką?

Wiem jak użyć tee do zapisu wyjścia (STDOUT) z aaa.sh do bbb.out, jednocześnie wyświetlając go w terminalu:

./aaa.sh | tee bbb.out

Jak mam teraz napisać STDERR do pliku o nazwie ccc.out, mając go nadal wyświetlany?

Author: Mark Stewart, 2009-03-28

10 answers

Zakładam, że nadal chcesz widzieć STDERR i STDOUT na terminalu. Możesz skorzystać z odpowiedzi Josha Kelleya, ale uważam, że trzymanie tail wokół w tle, co sprawia, że Twój plik dziennika jest bardzo hakerski i grudkowy. Zauważ, jak należy zachować exra FD i zrobić sprzątanie po nim, zabijając go i technicznie powinieneś to robić w trap '...' EXIT.

Jest na to lepszy sposób, a ty już to odkryłeś: tee.

Tylko, zamiast po prostu używać go do swojego stdout, miej tee dla stdout i jeden dla stderr. Jak tego dokonasz? Podstawianie procesów i przekierowywanie plików:

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

Podzielmy się i wyjaśnijmy:

> >(..)

>(...) (process substitution) tworzy FIFO i pozwala tee słuchać na nim. Następnie używa > (przekierowanie plików), aby przekierować STDOUT z command do FIFO, którego słucha twoja pierwsza tee.

To samo dla drugiego:

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

Ponownie używamy podstawienia procesu, aby tee proces, który odczytuje ze standardowego wejścia i wrzuca go do stderr.log. tee wysyła swoje wejście z powrotem na STDOUT, ale ponieważ jego wejście jest naszym STDERR, chcemy ponownie przekierować STDOUT tee na nasz STDERR. Następnie używamy przekierowania plików, aby przekierować STDERR command na wejście FIFO (tee'S STDIN).

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

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


W sh, musiałbyś 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"
 853
Author: lhunath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-21 13:28:50

Dlaczego nie po prostu:

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

To po prostu przekierowuje stderr do stdout, więc tee wyświetla zarówno log, jak i screen. Może coś mi umyka, bo niektóre inne rozwiązania wydają się naprawdę skomplikowane.

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

./aaa.sh |& tee -a log
 746
Author: user542833,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-13 11:42:20

Może to być przydatne dla osób, które znajdują to za pośrednictwem google. Po prostu odkomentuj przykład, który chcesz wypróbować. Oczywiście możesz zmienić nazwy 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}
 68
Author: Anthony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-09 18:21:29

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

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

EDIT: aby wyświetlić zarówno stderr, jak i stdout na ekranie, a także zapisać oba do pliku, możesz użyć przekierowania I/O Basha:

#!/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
 22
Author: Josh Kelley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-28 03:40:18

Innymi słowy, chcesz podłączyć stdout do jednego filtra (tee bbb.out) i stderr do innego filtra (tee ccc.out). Nie ma standardowego sposobu przesyłania czegokolwiek innego niż stdout do innego polecenia, ale można to obejść ż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 grep standard error stream (stderr)? i Kiedy można użyć dodatkowego deskryptora pliku?

W bash (oraz ksh i zsh), ale nie w innych powłokach POSIX, takich jak dash, można użyć podstawienie procesu :

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

Uważaj, że w bash polecenie to powraca natychmiast po zakończeniu ./aaa.sh, nawet jeśli tee polecenia są nadal wykonywane (ksh i zsh czekają na podprocesy). Może to być problem, jeśli zrobisz coś takiego jak ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. W takim przypadku zamiast tego użyj deskryptora pliku lub KSH / zsh.

 19
Author: Gilles 'SO- stop being evil',
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:31

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 (nie odpowiadam z czubka głowy) idzie tutaj: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html

 14
Author: ChristopheD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-28 02:00:05

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

cmd > log 2>&1

Musiałem go zaktualizować tak, że gdy wystąpi awaria, podjąć pewne działania w oparciu o komunikaty o błędach. Mógłbym oczywiście usunąć dup 2>&1 i przechwycić stderr ze skryptu, ale wtedy komunikaty o błędach nie trafią do pliku dziennika w celach informacyjnych. Podczas gdy zaakceptowana odpowiedź od @lhunath ma zrobić to samo, przekierowuje stdout i stderr na inne pliki, co nie jest tym, czego chcę, ale pomogło mi wymyślić dokładne rozwiązanie, którego potrzebuję:

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

Z powyższego log będzie miał kopię stdout i stderr i mogę przechwycić stderr z mojego skryptu bez martwienia się o stdout.

 5
Author: haridsv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-31 12:41:38

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 samego siebie i 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

Należy pamiętać, że wymaga to ustawienia opcji MULTIOS (która jest domyślna).

MULTIOS

Wykonywać implicit tee S lub cat s przy próbie wielokrotnego przekierowania (zobacz przekierowanie ).

 5
Author: Arminius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-27 18:42:02

W przypadku KornShell(ksh), gdzie zastępowanie procesu nie jest dostępne, należy wykonać następujące czynności.]}

# 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>&-

Prawdziwą sztuczką jest sekwencja 2>&1 1>&3, która w naszym przypadku przekierowuje stderr na stdout i przekierowuje stdout na deskryptor 3. W tym momencie stderr i stdout nie są jeszcze połączone.

W efekcie, stderr(jako stdin) jest przekazywany do tee, gdzie loguje się do stderr.log, a także przekierowuje do deskryptora 3.

I deskryptor 3 zapisuje go do Cały czas. Zatem combined.log zawiera zarówno stdout, jak i stderr.

 4
Author: shuva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-31 23:08:41

Podobnie jak przyjęta odpowiedź dobrze wyjaśniona przez lhunath , możesz użyć

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

Uważaj, niż Jeśli używasz Basha, możesz mieć jakiś problem .

Pozwól mi wziąć matthew-wilcoxson exemple.

A dla tych, którzy "widzieć znaczy wierzyć", szybki test:]}
(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)

Osobiście, kiedy próbuję, mam taki wynik:

user@computer:~$ (echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
user@computer:~$ Test Out
Test Err

Obie wiadomości nie pojawiają się na tym samym poziomie. Why Test Out seem to be site like if it is my previous dowództwo ?
Prompt jest na pustej linii, pozwól mi myśleć, że proces nie jest zakończony, a kiedy nacisnę {[10] } to to naprawić.
Kiedy sprawdzam zawartość plików, jest ok, przekierowanie działa.

Zróbmy kolejny test.
function outerr() {
  echo "out"     # stdout
  echo >&2 "err" # stderr
}

user@computer:~$ outerr
out
err

user@computer:~$ outerr >/dev/null
err

user@computer:~$ outerr 2>/dev/null
out

Próbuje ponownie przekierować, ale z tą funkcją.

function test_redirect() {
  fout="stdout.log"
  ferr="stderr.log"
  echo "$ outerr"
  (outerr) > >(tee "$fout") 2> >(tee "$ferr" >&2)
  echo "# $fout content :"
  cat "$fout"
  echo "# $ferr content :"
  cat "$ferr"
}

Osobiście mam taki wynik:

user@computer:~$ test_redirect
$ outerr
# stdout.log content :
out
out
err
# stderr.log content :
err
user@computer:~$

Brak zachęty na pustej linii, ale nie widzę normalnego wyjścia, stdout.zawartość dziennika wydaje się być błędna, tylko stderr./ align = "left" / ok. Jeśli go ponownie uruchomić, wyjście może być inne...

Więc dlaczego ?

Ponieważ, jak wyjaśniono tutaj :

Uważaj, że w bash Komenda ta powraca natychmiast po zakończeniu [pierwsze polecenie], nawet jeśli polecenia tee są nadal wykonywane (KSH i zsh czekają na podprocesy)

Więc, jeśli używasz Basha, preferuj użycie lepszego exemple podanego w ta inna odpowiedź :

{ { outerr | tee "$fout"; } 2>&1 1>&3 | tee "$ferr"; } 3>&1 1>&2

Naprawi poprzednie problemy.

Teraz, pytanie brzmi, jak odzyskać kod statusu zakończenia ?
$? nie działa.
Nie znalazłem lepszego rozwiązania niż przełącznik na pipefail z set -o pipefail (set +o pipefail aby wyłączyć) i użyć ${PIPESTATUS[0]} w ten sposób

function outerr() {
  echo "out"
  echo >&2 "err"
  return 11
}

function test_outerr() {
  local - # To preserve set option
  ! [[ -o pipefail ]] && set -o pipefail; # Or use second part directly
  local fout="stdout.log"
  local ferr="stderr.log"
  echo "$ outerr"
  { { outerr | tee "$fout"; } 2>&1 1>&3 | tee "$ferr"; } 3>&1 1>&2
  # First save the status or it will be lost
  local status="${PIPESTATUS[0]}" # Save first, the second is 0, perhaps tee status code.
  echo "==="
  echo "# $fout content :"
  echo "<==="
  cat "$fout"
  echo "===>"
  echo "# $ferr content :"
  echo "<==="
  cat "$ferr"
  echo "===>"
  if (( status > 0 )); then
    echo "Fail $status > 0"
    return "$status" # or whatever
  fi
}
user@computer:~$ test_outerr
$ outerr
err
out
===
# stdout.log content :
<===
out
===>
# stderr.log content :
<===
err
===>
Fail 11 > 0
 0
Author: B Jam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-12 11:12:20