przekieruj KOPIĘ stdout do pliku dziennika z poziomu samego skryptu bash

235

Wiem, jak przekierować standardowe wyjście do pliku:

exec > foo.log
echo test

spowoduje to umieszczenie „testu” w pliku foo.log.

Teraz chcę przekierować dane wyjściowe do pliku dziennika i zachować je na standardowym wyjściu

tzn. można to zrobić trywialnie spoza skryptu:

script | tee foo.log

ale chcę to zadeklarować w samym skrypcie

próbowałem

exec | tee foo.log

ale to nie zadziałało.

Witalij Kushner
źródło
3
Twoje pytanie jest źle sformułowane. Kiedy wywołujesz „exec> foo.log”, standardowym skryptem jest plik foo.log. Myślę, że to znaczy, że chcesz iść do wyjścia foo.log i do tty, ponieważ będzie foo.log się dzieje na standardowe wyjście.
William Pursell,
chciałbym użyć | na „exec”. byłoby to dla mnie idealne, tzn. „exec | tee foo.log”, niestety nie można użyć przekierowania potoku w wywołaniu exec
Vitaly Kushner

Odpowiedzi:

297
#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

Zauważ, że tak bashnie jest sh. Jeśli wywołasz skrypt za pomocą sh myscript.sh, pojawi się błąd wzdłuż linii syntax error near unexpected token '>'.

Jeśli pracujesz z pułapkami sygnałowymi, możesz użyć tee -iopcji, aby uniknąć zakłóceń na wyjściu w przypadku wystąpienia sygnału. (Podziękowania dla JamesThomasMoon1979 za komentarz.)


Narzędzia, które zmieniają dane wyjściowe w zależności od tego, czy zapisują na potoku, czy na terminalu (ls na przykład używając kolorów i skolonizowanych danych wyjściowych) wykryją powyższą konstrukcję jako oznaczającą, że wysyłają dane do rury.

Istnieją opcje wymuszenia kolorowania / kolumnizacji (np ls -C --color=always.). Zauważ, że spowoduje to zapisanie kodów kolorów również w pliku dziennika, co czyni go mniej czytelnym.

DevSolar
źródło
5
Trójnik w większości systemów jest buforowany, więc dane wyjściowe mogą nie zostać dostarczone, dopóki skrypt się nie zakończy. Ponieważ ten trójnik działa w podpowłoce, a nie w procesie potomnym, nie można użyć funkcji wait do synchronizacji danych wyjściowych z procesem wywołującym. To, czego chcesz, to niebuforowana wersja tee podobna do bogomips.org/rainbows.git/commit/…
14
@ Barry: POSIX określa, że teenie powinien buforować danych wyjściowych. Jeśli buforuje w większości systemów, jest uszkodzony w większości systemów. To jest problem teeimplementacji, a nie mojego rozwiązania.
DevSolar
3
@Sebastian: execjest bardzo potężny, ale także bardzo zaangażowany. Możesz „wykonać kopię zapasową” bieżącego standardu do innego skryptu plików, a następnie odzyskać go później. Google „samouczek bash exec”, istnieje wiele zaawansowanych rzeczy.
DevSolar,
2
@AdamSpiers: Nie jestem też pewien, o czym był Barry. Bash użytkownika execjest udokumentowane , aby nie rozpocząć nowe procesy, >(tee ...)to standard nazwany podstawienie rura / procesu, a &w przekierowania oczywiście nie ma nic wspólnego ze w tło ... :-)?
DevSolar
11
Sugeruję przejście -ido tee. W przeciwnym razie przerwanie sygnału (pułapki) zakłóci standardowe wyjście w głównym skrypcie. Na przykład, jeśli masz trap 'echo foo' EXITa następnie naciśnij ctrl+c, nie zobaczysz „ foo ”. Więc zmodyfikowałbym odpowiedź na exec &> >(tee -ia file).
JamesThomasMoon1979
173

Akceptowana odpowiedź nie zachowuje STDERR jako osobnego deskryptora pliku. To znaczy

./script.sh >/dev/null

nie będzie wyświetlać bardanych wyjściowych w terminalu, tylko w pliku dziennika i

./script.sh 2>/dev/null

wypisze zarówno fooi bardo terminala. Najwyraźniej takiego zachowania się nie spodziewa zwykły użytkownik. Można to naprawić za pomocą dwóch oddzielnych procesów tee dołączonych do tego samego pliku dziennika:

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(Zauważ, że powyższe nie początkowo obcina plik dziennika - jeśli chcesz tego zachowania, powinieneś dodać

>foo.log

na początek skryptu).

Specyfikacja POSIX.1-2008tee(1) wymaga, aby dane wyjściowe nie były buforowane, tzn. Nie są nawet buforowane w linii, więc w tym przypadku możliwe jest, że STDOUT i STDERR mogą znaleźć się w tym samym wierszu foo.log; może się to jednak zdarzyć na terminalu, więc plik dziennika będzie wiernym odzwierciedleniem tego, co można zobaczyć na terminalu, jeśli nie będzie jego dokładnym odzwierciedleniem. Jeśli chcesz, aby wiersze STDOUT były wyraźnie oddzielone od wierszy STDERR, rozważ użycie dwóch plików dziennika, być może z prefiksem datownika w każdym wierszu, aby umożliwić późniejsze ponowne składanie chronologiczne.

Adam Spiers
źródło
Z jakiegoś powodu, w moim przypadku, gdy skrypt jest wykonywany z wywołania system-c programu c, dwa podprocesy tee nadal istnieją nawet po wyjściu ze skryptu głównego. Musiałem więc dodać takie pułapki:exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
alveko,
15
Sugeruję przejście -ido tee. W przeciwnym razie przerwanie sygnału (pułapki) zakłóci standardowe wyjście w skrypcie. Na przykład, jeśli naciśniesz, trap 'echo foo' EXITa następnie ctrl+cnie zobaczysz „ foo ”. Więc zmodyfikowałbym odpowiedź na exec > >(tee -ia foo.log).
JamesThomasMoon1979
Na tej podstawie stworzyłem kilka „skryptów źródłowych”. Można ich używać w skrypcie, takim jak : . loglub . log foo.log: sam.nipl.net/sh/log sam.nipl.net/sh/log-a
Sam Watkins
1
Problem z tą metodą polega na tym, że wiadomości STDOUTpojawią się najpierw jako partia, a następnie wiadomości STDERR. Nie są przeplatane, jak zwykle się spodziewano.
CMCDragonkai
28

Rozwiązanie dla powłok busy busy, macOS bash i non-bash

Przyjęta odpowiedź jest z pewnością najlepszym wyborem na bash. Pracuję w środowisku Busybox bez dostępu do bash i nie rozumie exec > >(tee log.txt)składni. To też nie działaexec >$PIPE poprawnie, próbując utworzyć zwykły plik o tej samej nazwie co nazwany potok, który nie działa i zawiesza się.

Mam nadzieję, że przydałoby się to komuś, kto nie ma bashu.

Również dla każdego używającego nazwanego potoku jest to bezpieczne rm $PIPE , ponieważ powoduje to odłączenie potoku od VFS, ale procesy, które go używają, nadal utrzymują licznik referencyjny, dopóki nie zostaną zakończone.

Uwaga: użycie $ * niekoniecznie jest bezpieczne.

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here
jbarlow
źródło
To jedyne rozwiązanie, jakie do tej pory widziałem, które działa na komputerach Mac
Mike Baglio Jr.
19

W pliku skryptu umieść wszystkie polecenia w nawiasach:

(
echo start
ls -l
echo end
) | tee foo.log
WReach
źródło
5
pedantycznie, mógłby również użyć nawiasów klamrowych ( {})
glenn jackman
no tak, zastanowiłem się nad tym, ale to nie jest przekierowanie obecnego standardu powłoki, jest to rodzaj oszustwa, faktycznie uruchamiasz podpowłokę i robisz na niej zwykłe przekierowywanie piperami. działa myśl. Jestem podzielony z tym i rozwiązaniem „tail -f foo.log &”. zaczekam chwilę, by zobaczyć, czy może być lepsza. jeśli nie zamierza się osiedlić;)
Witalij Kushner
8
{} wykonuje listę w bieżącym środowisku powłoki. () wykonuje listę w środowisku podpowłoki.
Cholera. Dziękuję Ci. Przyjęta tam odpowiedź nie działała dla mnie, próbując zaplanować uruchomienie skryptu pod MingW w systemie Windows. Uważam, że narzekałem na niewdrożoną substytucję procesu. Ta odpowiedź zadziałała dobrze, po następującej zmianie, aby uchwycić zarówno stderr, jak i stdout: `` -) | tee foo.log +) 2> i 1 | tee foo.log
Jon Carter
14

Łatwy sposób na utworzenie dziennika skryptu bash do syslog. Dane wyjściowe skryptu są dostępne zarówno przez, jak /var/log/syslogi przez stderr. syslog doda przydatne metadane, w tym znaczniki czasu.

Dodaj tę linię u góry:

exec &> >(logger -t myscript -s)

Ewentualnie wyślij dziennik do osobnego pliku:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

Wymaga to moreutils(dla tspolecenia, które dodaje znaczniki czasu).

Tobu
źródło
10

Korzystając z zaakceptowanej odpowiedzi, mój skrypt wracał wyjątkowo wcześnie (zaraz po „exec>> (tee ...)”), pozostawiając resztę skryptu działającą w tle. Ponieważ nie mogłem zmusić tego rozwiązania do działania, znalazłem inne rozwiązanie / obejście problemu:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

Powoduje to, że dane wyjściowe skryptu przechodzą z procesu, przez potok do procesu podrzędnego procesu „tee”, który rejestruje wszystko na dysku i do oryginalnego standardowego skryptu.

Zauważ, że „exec &>” przekierowuje zarówno stdout, jak i stderr, możemy je przekierować osobno, jeśli chcemy, lub zmienić na „exec>”, jeśli chcemy tylko stdout.

Nawet jeśli potok zostanie usunięty z systemu plików na początku skryptu, będzie działał do momentu zakończenia procesów. Po prostu nie możemy odwoływać się do niego przy użyciu nazwy pliku po linii rm.

palec
źródło
Podobna odpowiedź jako drugi pomysł Davida Z . Zobacz jej komentarze. +1 ;-)
olibre
Działa dobrze. Nie rozumiem $logfileczęści tee < ${logfile}.pipe $logfile &. W szczególności próbowałem to zmienić, aby przechwycić pełne rozwinięte wiersze dziennika poleceń (z set -x) do pliku, pokazując tylko wiersze bez wiodącego „+” na standardowym wyjściu, zmieniając na, (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &ale otrzymałem komunikat o błędzie dotyczący $logfile. Czy możesz wyjaśnić teelinię nieco bardziej szczegółowo?
Chris Johnson
Przetestowałem to i wydaje się, że ta odpowiedź nie zachowuje STDERR (jest scalony ze STDOUT), więc jeśli polegasz na oddzielnych strumieniach w celu wykrycia błędu lub innego przekierowania, powinieneś spojrzeć na odpowiedź Adama.
HeroCC
2

Bash 4 ma coprocpolecenie, które ustanawia nazwany potok do polecenia i umożliwia komunikację za jego pośrednictwem.

Wstrzymano do odwołania.
źródło
1

Nie mogę powiedzieć, że czuję się swobodnie z jakimkolwiek rozwiązaniem opartym na exec. Wolę używać tee bezpośrednio, więc na żądanie wykonuję skrypt w tee:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

To pozwala ci to zrobić:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

Możesz to dostosować, np. Ustaw tee = false jako wartość domyślną, zamiast tego TEE zatrzyma plik dziennika, itd. Myślę, że to rozwiązanie jest podobne do rozwiązania jbarlow, ale prościej, może moje ograniczenia, z którymi jeszcze się nie spotkałem.

Oliver
źródło
-1

Żadne z nich nie jest idealnym rozwiązaniem, ale oto kilka rzeczy, które możesz wypróbować:

exec >foo.log
tail -f foo.log &
# rest of your script

lub

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

Drugi zostawiłby plik potoku na siedzeniu, jeśli coś pójdzie nie tak ze skryptem, co może, ale nie musi stanowić problemu (tj. Może rmpóźniej możesz to zrobić w powłoce nadrzędnej).

David Z
źródło
1
tail pozostawi działający proces w 2. tee skryptu zostanie zablokowany, lub będziesz musiał go uruchomić za pomocą iw którym to przypadku proces pozostanie jak w pierwszym.
Witalij Kushner,
@Vitaly: ups, zapomniałem o tle tee- edytowałem. Jak powiedziałem, żadne z nich nie jest idealnym rozwiązaniem, ale procesy w tle zostaną zabite, gdy ich macierzysta powłoka zakończy działanie, więc nie musisz się martwić, że na zawsze zablokują zasoby.
David Z
1
Yikes: wyglądają atrakcyjnie, ale wynik działania tail -f będzie również dostępny na foo.log. Możesz to naprawić, uruchamiając tail -f przed exec, ale ogon nadal działa po zakończeniu rodzica. Musisz go wyraźnie zabić, prawdopodobnie w pułapce 0
William Pursell
Tak Jeśli skrypt jest w tle, pozostawia procesy w całości.