Zapisywanie danych wyjściowych do pliku dziennika i konsoli

99

W powłoce systemu Unix mam plik env (plik env definiuje parametry wymagane do uruchomienia skryptu użytkownika, takie jak nazwa pliku dziennika i ścieżka, przekierowuje wyjścia i błędy do pliku dziennika, szczegóły połączenia z bazą danych itp. ), Który przekierowuje wszystkie wyjścia ( komunikaty echo ) i błędy do pliku dziennika z wykonywanego skryptu przy użyciu następującego kodu:

exec 1>>${LOG_FILE}
exec 2>>${LOG_FILE}

Plik env jest wykonywany na początku każdego skryptu. Ze względu na powyższy kod w pliku env, wszystkie wyjścia konsoli, które mogą być wyjściami użytkownika lub błędami, są bezpośrednio wyprowadzane do pliku dziennika, którego faktycznie potrzebowałem.

Ale jest kilka wybranych wyników użytkownika, które chcę, aby były wyświetlane zarówno w konsoli, jak iw pliku dziennika. Ale z powodu powyższego kodu nie jestem w stanie tego zrobić.

Wiem, że jeśli usunę powyższy kod, mogę uzyskać pożądany wynik w tym przypadku, ale będę musiał ręcznie zapisać wszystkie inne wyniki do pliku dziennika, co nie jest łatwym zadaniem.

Czy istnieje sposób, aby uzyskać dane wyjściowe zarówno w konsoli, jak i pliku dziennika bez usuwania powyższych kodów?

abinash shrestha
źródło

Odpowiedzi:

106
exec 3>&1 1>>${LOG_FILE} 2>&1

wyśle ​​wyjście stdout i stderr do pliku dziennika, ale zostawiłoby również fd 3 podłączone do konsoli, więc możesz zrobić

echo "Some console message" 1>&3

napisać wiadomość tylko do konsoli, lub

echo "Some console and log file message" | tee /dev/fd/3

aby napisać wiadomość zarówno do konsoli, jak i do pliku dziennika - teewysyła swoje dane wyjściowe zarówno do swojego własnego fd 1 (który tutaj jest LOG_FILE), jak i do pliku, w którym kazałeś mu zapisywać (który tutaj jest fd 3, czyli konsola).

Przykład:

exec 3>&1 1>>${LOG_FILE} 2>&1

echo "This is stdout"
echo "This is stderr" 1>&2
echo "This is the console (fd 3)" 1>&3
echo "This is both the log and the console" | tee /dev/fd/3

drukuje

This is the console (fd 3)
This is both the log and the console

na konsoli i włóż

This is stdout
This is stderr
This is both the log and the console

do pliku dziennika.

Ian Roberts
źródło
2
Zadziałało tak, jak sugerowałeś. Ale nie rozumiałem tee / dev / fd / 3 . Wiem, że tee zapisuje wiadomość do pliku dziennika i konsoli, ale nie do końca rozumiałem / dev / fd / 3 używane po tee
abinash shrestha.
@shrestha to niezwykłe użycie koszulki, zgadzam się. Jest /dev/fd/3to nazwa pliku, która odnosi się do "aktualnie otwartego fd 3", więc tee /dev/fd/3zapisze wszystko, co pojawia się na jego stdin, do fd 1, a także fd 3 ( /dev/fd/3plik). Fd 1 jest podłączony do pliku dziennika, fd 3 jest podłączony do konsoli.
Ian Roberts
Chcesz również pisać tylko do pliku, a nie do konsoli, spróbuj: echo "blah" | tee file1.txt | tee file2.txt >/dev/null „Blah” nie zostanie umieszczony w plik1.txt i plik2.txt, ale nie zostanie zapisany w konsoli.
niebezpieczeństwo89
To było dla mnie bardzo pomocne. Chociaż zauważyłem coś, co może być nie na temat, ale może niektórzy z was znają przyczyny tego. Wykonuję skrypty R ze skryptu bash. Wyjście konsoli różnych funkcji R jest kolorowe (tak jak to zdefiniowałem). kiedy używasz powyższego podejścia do przekierowania wyjścia do pliku dziennika konsoli AND, wyjście konsoli nie jest już kolorowane. Jaki może być tego powód?
dieHellste
1
@dieHellste niektóre programy są w stanie wykryć, kiedy ich dane wyjściowe są przesyłane potokiem do innego procesu (w tym przypadku tee, który z kolei zapisuje do terminala), zamiast przechodzić bezpośrednio do terminala, i dostosowują swoje wyjście, aby pasowało.
Ian Roberts
43

Tak, chcesz użyć tee:

tee - czyta ze standardowego wejścia i zapisuje na standardowe wyjście i pliki

Po prostu prześlij polecenie do tee i przekaż plik jako argument, na przykład:

exec 1 | tee ${LOG_FILE}
exec 2 | tee ${LOG_FILE}

Spowoduje to wydrukowanie danych wyjściowych do STDOUT i zapisanie tych samych danych wyjściowych w pliku dziennika. Zobacz, man teeaby uzyskać więcej informacji.

Zauważ, że to nie zapisze stderr do pliku dziennika, więc jeśli chcesz połączyć dwa strumienie, użyj:

exec 1 2>&1 | tee ${LOG_FILE}
Jon Cairns
źródło
2
Powyższe rozwiązanie nie zadziałało. Mam plik redirect.env jako: ##### redirect.env ###### eksport LOG_FILE = log.txt exec 1 2> & 1 | tee -a $ {PLIK_LOGU} exec 1 | tee -a $ {PLIK_LOGU} exec 2 | tee -a $ {PLIK_LOGU} #########, a plik roboczy zawierał następujący kod: ##### output.sh ##### #! / bin / sh. redirect.env echo "Prawidłowe wyjście" ech "nieprawidłowe wyjście" #############, ale otrzymuję błąd: #### redirect.env: line 3: exec: 1: not found redirect.env: line 5: exec: 1: not found redirect.env: line 6: exec: 2: not found #### iw pliku dziennika również otrzymuję ten sam błąd. Czy robię coś złego?
abinash shrestha
Trudno to stwierdzić, ponieważ w Twoim komentarzu nowe wiersze zostały usunięte. Można dodać pastę kodu na gdzieś jak GIST ?
Jon Cairns
1
Dodałem pliki do odsyłacza UnixRedirect . Powiązane pliki to redirect.env i output.sh
abinash shrestha
1
Ten kod nie wydaje się działać (przynajmniej już nie). Zwykle otrzymujeszscript.sh: line 5: exec: 1: not found
tftd
39

Wypróbowałem odpowiedź joonty'ego, ale dostałem też

exec: 1: nie znaleziono

błąd. To, co działa najlepiej dla mnie ( potwierdzone, że działa również w zsh):

#!/bin/bash
LOG_FILE=/tmp/both.log
exec > >(tee ${LOG_FILE}) 2>&1
echo "this is stdout"
chmmm 77 /makeError

Plik /tmp/both.log zawiera następnie

this is stdout
chmmm command not found 

Dziennik /tmp/both.log jest dołączany, chyba że usuniesz -a z tee.

Wskazówka: >(...)jest substytucją procesu. To pozwala execna teepolecenia tak, jakby był to plik.

alfonx
źródło
1
To działało jak urok dla mnie, podczas gdy inne odpowiedzi były trafione lub chybione.
Jay Taylor
Dzięki za udostępnienie tego fragmentu. Wygląda na to, że działa dobrze (w porównaniu z innymi odpowiedziami)!
tftd
To działa, ale teraz moja powłoka jest inna po wykonaniu.
Josh Usre
@JoshUsre Jeśli chcesz żadnej pomocy lub chcesz pomóc użytkownikom inne tak, proszę wyjaśnić, co jest inna, i opisać swoją skorupę, wersję systemu operacyjnego, itp ..
alfonx
1
Jeśli nie musisz rozróżniać między stdout i stderr, możesz połączyć instrukcje z exec > >(tee ${LOG_FILE}) 2>&1.
darkdragon
5

Chciałem wyświetlić logi na stdout i plik dziennika wraz ze znacznikiem czasu. Żadna z powyższych odpowiedzi nie zadziałała dla mnie. Skorzystałem z podstawiania procesu i polecenia exec i wymyśliłem następujący kod. Przykładowe dzienniki:

2017-06-21 11:16:41+05:30 Fetching information about files in the directory...

Dodaj następujące wiersze u góry skryptu:

LOG_FILE=script.log
exec > >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done)
exec 2> >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done >&2)

Mam nadzieję, że to komuś pomoże!

Jyoti Dhiman
źródło
Czy można rejestrować wszystkie błędy aplikacji, aby zalogować się do katalogu hosta, aby pomóc devopsowi ...
Prasad Shinde
3

w przypadku pliku dziennika możesz wprowadzić datę do danych tekstowych. poniższy kod może pomóc

# declaring variables

Logfile="logfile.txt"   
MAIL_LOG="Message to print in log file"  
Location="were is u want to store log file"

cd $Location   
if [ -f $Logfile ]  
then   
echo "$MAIL_LOG " >> $Logfile

else        

touch $Logfile   
echo "$MAIL_LOG" >> $Logfile    

fi  

ouput: 2. Plik dziennika zostanie utworzony przy pierwszym uruchomieniu i będzie aktualizowany od następnych uruchomień. W przypadku braku pliku dziennika w przyszłości skrypt utworzy nowy plik dziennika.

user2197712
źródło
1

Znalazłem sposób na uzyskanie pożądanego wyniku. Choć może to być nieco nieortodoksyjny sposób. Tak czy inaczej, to idzie. W pliku redir.env mam następujący kod:

#####redir.env#####    
export LOG_FILE=log.txt

      exec 2>>${LOG_FILE}

    function log {
     echo "$1">>${LOG_FILE}
    }

    function message {
     echo "$1"
     echo "$1">>${LOG_FILE}
    }

Następnie w rzeczywistym skrypcie mam następujące kody:

#!/bin/sh 
. redir.env
echo "Echoed to console only"
log "Written to log file only"
message "To console and log"
echo "This is stderr. Written to log file only" 1>&2

Tutaj echo wyprowadza tylko do konsoli, dane wyjściowe dziennika tylko do pliku dziennika, a wyjścia komunikatów do pliku dziennika i konsoli.

Po wykonaniu powyższego pliku skryptu mam następujące wyjścia:

W konsoli

W konsoli
Echo tylko
do konsoli Do konsoli i dziennika

Dla pliku dziennika

W pliku dziennika Zapisano tylko do pliku dziennika
To jest stderr. Zapisany tylko do pliku dziennika
Do konsoli i dziennika

Mam nadzieję, że to pomoże.

abinash shrestha
źródło
1

Spróbuj tego, zadziała:

log_file=$curr_dir/log_file.txt
exec > >(tee -a ${log_file} )
exec 2> >(tee -a ${log_file} >&2)
amousa
źródło
To exec > >(tee -a ${log_file} )działa idealny dla moich potrzeb. Poprzednie rozwiązania powyżej przerywałyby części mojego skryptu, które wymuszałyby wyjście, gdyby zawiodły. Dziękuję
MitchellK
1
    #
    #------------------------------------------------------------------------------
    # echo pass params and print them to a log file and terminal
    # with timestamp and $host_name and $0 PID
    # usage:
    # doLog "INFO some info message"
    # doLog "DEBUG some debug message"
    # doLog "WARN some warning message"
    # doLog "ERROR some really ERROR message"
    # doLog "FATAL some really fatal message"
    #------------------------------------------------------------------------------
    doLog(){
        type_of_msg=$(echo $*|cut -d" " -f1)
        msg=$(echo "$*"|cut -d" " -f2-)
        [[ $type_of_msg == DEBUG ]] && [[ $do_print_debug_msgs -ne 1 ]] && return
        [[ $type_of_msg == INFO ]] && type_of_msg="INFO " # one space for aligning
        [[ $type_of_msg == WARN ]] && type_of_msg="WARN " # as well

        # print to the terminal if we have one
        test -t 1 && echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg"

        # define default log file none specified in cnf file
        test -z $log_file && \
            mkdir -p $product_instance_dir/dat/log/bash && \
                log_file="$product_instance_dir/dat/log/bash/$run_unit.`date "+%Y%m"`.log"
        echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg" >> $log_file
    }
    #eof func doLog
Yordan Georgiev
źródło
0

Uważam, że bardzo przydatne jest dołączanie zarówno stdout, jak i stderr do pliku dziennika. Byłem zadowolony, widząc rozwiązanie alfonx z exec > >(tee -a), ponieważ zastanawiałem się, jak to zrobić za pomocą exec. Natrafiłem na kreatywne rozwiązanie wykorzystujące składnię here-doc i .: /unix/80707/how-to-output-text-to-both-screen-and-file-inside-a-shell -scenariusz

Odkryłem, że w zsh rozwiązanie here-doc można zmodyfikować za pomocą konstrukcji „multios” w celu skopiowania danych wyjściowych zarówno do stdout / stderr, jak i do pliku dziennika:

#!/bin/zsh
LOG=$0.log
# 8 is an arbitrary number;
# multiple redirects for the same file descriptor 
# triggers "multios"
. 8<<\EOF /dev/fd/8 2>&2 >&1 2>>$LOG >>$LOG
# some commands
date >&2
set -x
echo hi
echo bye
EOF
echo not logged

Nie jest tak czytelny jak execrozwiązanie, ale ma tę zaletę, że pozwala na zalogowanie tylko części skryptu. Oczywiście, jeśli pominiesz EOF, cały skrypt zostanie wykonany z logowaniem. Nie jestem pewien, jak zshimplementuje multio, ale może to mieć mniejszy narzut niż tee. Niestety wydaje się, że nie można używać multio z exec.

Metamorficzny
źródło