Pokaż tylko stderr na ekranie, ale zapisz zarówno stdout, jak i stderr do pliku


23

Jak mogę użyć magii BASH, aby to osiągnąć?
Chcę widzieć tylko wyjście stderr na ekranie,
ale chcę, aby zarówno stdout, jak i stderr były zapisywane w pliku.

Wyjaśnienie: Chcę, aby zarówno stdout, jak i stderr znalazły się w tym samym pliku. W kolejności, w jakiej się znajdują.
Niestety żadna z poniższych odpowiedzi tego nie robi.


2
Nowe podejście do tego samego podstawowego problemu: jak przechwycić zamówiony STDOUT / STDERR i dodać znacznik czasu / prefiksy?
Gilles

Odpowiedzi:


14

Nawet bez przekierowania lub bez niczego >logfile 2>&1nie masz gwarancji, że zobaczysz wynik w kolejności generowania.

Na początek stdout z aplikacji będzie buforowany liniowo (do tty) lub buforowany (do potoku), ale stderr nie jest buforowany, więc relacje między kolejnością danych wyjściowych są zerwane w odniesieniu do czytnika. Kolejne etapy w dowolnym potoku, które można wymyślić, nie otrzymają deterministycznie uporządkowanego dostępu do dwóch strumieni (są to koncepcyjnie rzeczy dziejące się równolegle, a ty zawsze podlegasz harmonogramowi - jeśli do czasu, gdy czytelnik otrzyma wycinek, pisarz już ma napisane na obu potokach, nie wiadomo, które były pierwsze).

„[T] rozkaz, żeby się zdarzyły” jest tak naprawdę znany tylko aplikacji. Kolejność produkcji w stdout / stderr jest dobrze znanym - być może klasycznym - problemem.


7

Podczas korzystania z konstrukcji: 1>stdout.log 2>&1zarówno stderr, jak i stdout zostają przekierowane do pliku, ponieważ przekierowanie stdout jest konfigurowane przed przekierowaniem stderr .

Jeśli odwrócisz kolejność, możesz zostać przekierowany na stdout do pliku, a następnie skopiuj stderr na stdout, aby móc go potokować tee.

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!

To nie pozwala zalogować dwóch strumieni do tego samego pliku.
artistoex

1
@artistoex: Możesz po prostu użyć tee -atego samego pliku użytego 1>do zsumowania obu danych wyjściowych w tym samym pliku. Coś w stylu:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya,

Nie mam pojęcia dlaczego, ale to nie działa wget -O - www.google.dedla mnie. (porównaj z rozwiązaniem poniżej)
artistoex

4
Używanie tee -anie zadziała niezawodnie, aby uzyskać takie same dane wyjściowe, jakie byłyby na konsoli, gdyby nic nie zostało przekierowane. Dane wyjściowe, które przechodzą, teemogą być nieco opóźnione w porównaniu z danymi wyjściowymi pochodzącymi bezpośrednio z polecenia, a więc mogą pojawić się później w dzienniku.
Gilles

To nie działa dla mnie, out + err.log jest pusty. I tak, chcę, żeby w tym samym pliku pojawił się standard i standard
Andreas

7

Udało mi się to osiągnąć, umieszczając następujący wiersz na górze skryptu bash:

exec 1>>log 2> >(tee -a log >&2)

Spowoduje to przekierowanie stdout do file log( 1>>log), a następnie przeniesienie stderr do file log( 2> >(tee -a log) i skierowanie go z powrotem do stderr ( >&2). W ten sposób otrzymuję pojedynczy plik log, który pokazuje kolejno stdout i stderr, a stderr jest również wyświetlane na ekranie jak zwykle.

Problem polega na tym, że wydaje się, że działa tylko wtedy, gdy dołączam do pliku. Jeśli się nie dołączę, wygląda na to, że te dwa przekierowania blokują się i otrzymuję tylko jedno wyjście jako ostatnie.


Czy jest jakiś sposób, aby to „zmodyfikować” wiersze STDERR w celu włączenia niestandardowego ciągu, aby można go było łatwo grepować?
IMTheNachoMan

1

Chcesz zduplikować strumień błędów, aby pojawił się zarówno w konsoli, jak i w pliku dziennika. Narzędziem do tego jest teei wszystko, co musisz zrobić, to zastosować je do strumienia błędów. Niestety, nie ma standardowej konstrukcji powłoki umożliwiającej potokowanie strumienia błędów polecenia do innego polecenia, dlatego wymagana jest niewielka zmiana układu deskryptorów plików.

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log

To też nie do końca działa, najpierw otrzymuję linie stdout, a następnie wszystkie linie stderr w pliku „log”. przykład: {{echo out; echo err 1> & 2; echo out2; echo err2 1> & 2; } 2> i 1> i 3 | tee / dev / tty; }> log 3> i 1
Andreas

1
@Andreas: Oh, duh, teewprowadza tutaj również opóźnienie. Nie sądzę, aby istniało rozwiązanie oparte na czystej powłoce. W rzeczywistości zastanawiam się, czy istnieje rozwiązanie bez modyfikowania aplikacji w jakikolwiek sposób.
Gilles

1

Aby napisać stderr na screen i napisać ZARÓWNO stderr i stdout do pliku - ORAZ niech wiersze stderr i stdout wychodzą w takiej samej kolejności, jak gdyby oba były zapisane na ekranie:

Okazuje się, że jest to trudny problem, zwłaszcza część o posiadaniu „tej samej sekwencji”, której można by się spodziewać, gdyby po prostu napisać je na ekranie. W prostych słowach: zapisz każdy do własnego pliku, zrób trochę magii w tle, aby oznaczyć każdą linię (w każdym pliku) dokładnym czasem, w którym linia została utworzona, a następnie: „ogon - śledź” plik stderr na ekran , ale aby zobaczyć ZARÓWNO „stderr” i „stdout” razem - po kolei - posortuj oba pliki (z oznaczeniem dokładnego czasu w każdym wierszu) razem.

Kod:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

Tak, wydaje się to skomplikowane i daje w wyniku 4 pliki wyjściowe (z których 2 można usunąć). Wygląda na to, że jest to trudny problem do rozwiązania, więc wymagało kilku mechanizmów.

Na koniec, aby zobaczyć wyniki ZARÓWNO stdout i stderr w oczekiwanej sekwencji, uruchom to:

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

Jedynym powodem, dla którego sekwencja jest co najmniej bardzo zbliżona do tego, co by się stało, gdyby zarówno stdout, jak i stderr po prostu poszły na ekran, jest: Każda linia jest oznaczona znacznikiem czasu z dokładnością do milisekundy.

Aby zobaczyć stderr na ekranie w trakcie procesu, użyj tego:

tail -f $pth/$ffn.out

Mam nadzieję, że to pomoże komuś, kto przybył tu długo po zadaniu pierwotnego pytania.


+1 za zwykły wysiłek, miałem podobny pomysł (znaczniki czasu obu strumieni za pośrednictwem perla), ale starałem się go wdrożyć. Zbadam, czy to działa dla mnie (tj. Czy naprawdę zachowuje kolejność wydruków, ponieważ inne rozwiązania tutaj zmieniają nieco sytuację z powodu asynchroniczności między tagowaniem stderr i stdout)
Olivier Dulac

0

Niech fbędzie to polecenie, które chcesz wykonać, a potem to

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

powinien dać ci to, co chcesz. Na przykład wget -O - www.google.dewyglądałby tak:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )

1
To prawie działa dla mnie, ale w pliku dziennika najpierw otrzymuję wszystkie linie standardowe, a następnie wszystkie linie standardowe. Chcę, aby były one przeplatane w naturalnym porządku, gdy się zdarzają.
Andreas,

0

Biorąc pod uwagę to, co tutaj przeczytałem, jedynym rozwiązaniem, jakie widzę, byłoby wstawienie każdej linii STOUT lub STERR za pomocą szczeliny czasowej / znacznika czasu. date +% s przejdzie do sekund, ale to spowalnia, aby utrzymać porządek. Również dziennik musiałby jakoś zostać posortowany. Więcej pracy niż jestem w stanie.


0

Walczyłem z tym dokładnym wymaganiem i ostatecznie nie mogłem znaleźć prostego rozwiązania. Zamiast tego zrobiłem to:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

Dołącza stdout i stderr, w odpowiedniej kolejności przeplatania, do pliku dziennika. A jeśli wystąpią jakiekolwiek błędy (określone przez status wyjścia polecenia), wysyła całe wyjście (stdout i stderr) do stdout, ponownie w odpowiedniej kolejności z przeplotem. Uznałem, że jest to rozsądny kompromis dla moich potrzeb. Jeśli chcesz mieć plik dziennika z pojedynczym uruchomieniem, a nie rosnący plik z wieloma uruchomieniami, jest to jeszcze prostsze:

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log

0

stderr do podstawionego poleceniem tee -a, a stdout dołącza do tego samego pliku:

./script.sh 2> >(tee -a outputfile) >>outputfile

i uwaga: też zapewnij prawidłową kolejność (ale bez stderr-show) istnieje komenda „unbuffer” z narzędzi „expect”, która symuluje tty i utrzymuje kolejność stdout / err w kolejności, w jakiej byłaby wyświetlana na terminalu:

unbuffer ./script.sh > outputfile
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.