Chcę przekierować zarówno stdout, jak i stderr procesu do jednego pliku. Jak to zrobić w Bash?
Chcę przekierować zarówno stdout, jak i stderr procesu do jednego pliku. Jak to zrobić w Bash?
Odpowiedzi:
Spójrz tutaj . Powinien być:
yourcommand &>filename
(przekierowuje zarówno nazwę pliku, jak stdout
i stderr
nazwę pliku).
#!/bin/bash
od #!/bin/sh
, ponieważ in wymaga bash.
do_something 2>&1 | tee -a some_file
Spowoduje to przekierowanie stderr do stdout i stdout do some_file
i wydrukowanie go do stdout.
do_something &>filename
nie. +1.
Ambiguous output redirect.
pomysł, dlaczego?
$?
nie odnosi się już do statusu wyjścia do_something
, ale do statusu wyjścia tee
.
Możesz przekierować stderr na stdout, a stdout do pliku:
some_command >file.log 2>&1
Zobacz http://tldp.org/LDP/abs/html/io-redirection.html
Ten format jest preferowany niż najpopularniejszy &> format, który działa tylko w bash. W powłoce Bourne'a można to interpretować jako uruchamianie polecenia w tle. Również format jest bardziej czytelny 2 (jest STDERR) przekierowany na 1 (STDOUT).
EDYCJA: zmieniła kolejność, jak wskazano w komentarzach
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not 'on screen'"
Teraz proste echo zapisze się w $ LOG_FILE. Przydatny do demonizacji.
Do autora oryginalnego postu
To zależy od tego, co musisz osiągnąć. Jeśli musisz przekierować polecenie / polecenie z skryptu, odpowiedzi są już podane. Mój dotyczy przekierowania w ramach bieżącego skryptu, który wpływa na wszystkie polecenia / wbudowane (w tym widelce) po wspomnianym fragmencie kodu.
Innym fajnym rozwiązaniem jest przekierowanie zarówno do standardowego wyjścia / wyjścia ORAZ do programu rejestrującego lub pliku dziennika jednocześnie, co obejmuje podzielenie „strumienia” na dwa. Funkcję tę zapewnia komenda „tee”, która może zapisywać / dołączać jednocześnie do kilku deskryptorów plików (plików, gniazd, potoków itp.): Tee FILE1 FILE2 ...> (cmd1)> (cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''
local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
Tak więc od samego początku. Załóżmy, że mamy terminal podłączony do / dev / stdout (FD # 1) i / dev / stderr (FD # 2). W praktyce może to być rura, gniazdo lub cokolwiek innego.
Wynik uruchomienia skryptu o powyższej linii i dodatkowo tej:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
...następująco:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
Jeśli chcesz zobaczyć wyraźniejszy obraz, dodaj te 2 wiersze do skryptu:
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1>file.log
instruuje powłokę, aby wysłała STDOUT do pliku file.log
i 2>&1
każe przekierować STDERR (deskryptor pliku 2) do STDOUT (deskryptor pliku 1).
Uwaga: kolejność ma znaczenie, jak wskazano liw.fi, 2>&1 1>file.log
nie działa.
Co ciekawe, działa to:
yourcommand &> filename
Ale to powoduje błąd składniowy:
yourcommand &>> filename
syntax error near unexpected token `>'
Musisz użyć:
yourcommand 1>> filename 2>&1
&>>
wydaje się działać na BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
Krótka odpowiedź: Command >filename 2>&1
lubCommand &>filename
Wyjaśnienie:
Rozważ następujący kod, który wypisuje słowo „stdout” na stdout, a słowo „stderror” na stderror.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Zauważ, że operator „&” mówi bashowi, że 2 to deskryptor pliku (który wskazuje na stderr), a nie nazwa pliku. Jeśli pominiemy „&”, to polecenie wypisuje stdout
na standardowe wyjście, tworzy plik o nazwie „2” i stderror
tam zapisuje .
Eksperymentując z powyższym kodem, możesz przekonać się dokładnie, jak działają operatory przekierowania. Na przykład, zmieniając plik, który z dwóch deskryptorów 1,2
zostanie przekierowany do /dev/null
następujących dwóch wierszy kodu, usuń wszystko odpowiednio ze standardowego wyjścia, a wszystko odpowiednio ze standardowego błędu (wypisywanie pozostałych).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Teraz możemy wyjaśnić, dlaczego rozwiązanie, dlaczego poniższy kod nie generuje danych wyjściowych:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
Aby naprawdę to zrozumieć, zdecydowanie zalecamy przeczytanie tej strony w tabelach deskryptorów plików . Zakładając, że zrobiłeś to czytanie, możemy kontynuować. Zauważ, że Bash przetwarza od lewej do prawej; w ten sposób Bash widzi >/dev/null
pierwszy (który jest taki sam jak 1>/dev/null
) i ustawia deskryptor pliku 1 tak, aby wskazywał / dev / null zamiast standardowego wyjścia. Po zrobieniu tego Bash następnie porusza się w prawo i widzi 2>&1
. Ustawia to deskryptor pliku 2 tak, aby wskazywał na ten sam plik co deskryptor pliku 1 (a nie na sam deskryptor pliku 1 !!!! (zobacz ten zasób na temat wskaźnikówpo więcej informacji)) . Ponieważ deskryptor pliku 1 wskazuje na / dev / null, a deskryptor pliku 2 wskazuje na ten sam plik co deskryptor pliku 1, deskryptor pliku 2 teraz wskazuje również na / dev / null. Zatem oba deskryptory plików wskazują na / dev / null, i dlatego żadne wyjście nie jest renderowane.
Aby sprawdzić, czy naprawdę rozumiesz pojęcie, spróbuj zgadnąć dane wyjściowe, gdy zmienimy kolejność przekierowań:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
stderror
Powodem jest to, że oceniając od lewej do prawej, Bash widzi 2> i 1, a tym samym ustawia deskryptor pliku 2 tak, aby wskazywał to samo miejsce, co deskryptor pliku 1, tj. Stdout. Następnie ustawia deskryptor pliku 1 (pamiętaj, że> / dev / null = 1> / dev / null) wskazuje na> / dev / null, usuwając w ten sposób wszystko, co zwykle byłoby wysyłane do standardowego wyjścia. Zatem pozostaje nam tylko to, co nie zostało wysłane do standardowego wyjścia w podpowłoce (kod w nawiasach) - tzn. „Stderror”. Interesującą rzeczą do odnotowania jest to, że chociaż 1 jest tylko wskaźnikiem do standardowego wyjścia, przekierowanie wskaźnika 2 na 1 przez2>&1
NIE tworzy łańcucha wskaźników 2 -> 1 -> standardowego. Jeśli tak, w wyniku przekierowania 1 do / dev / null, kod2>&1 >/dev/null
dałby łańcuch wskaźnika 2 -> 1 -> / dev / null, a zatem kod nie wygenerowałby nic, w przeciwieństwie do tego, co widzieliśmy powyżej.
Na koniec zauważę, że istnieje prostszy sposób:
W sekcji 3.6.4 tutaj widzimy, że możemy użyć operatora &>
do przekierowania zarówno stdout, jak i stderr. Tak więc, aby przekierować zarówno wyjście stderr, jak i wyjście standardowe na dowolne polecenie \dev\null
(które usuwa dane wyjściowe), po prostu wpiszemy
$ command &> /dev/null
lub w przypadku mojego przykładu:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Najważniejsze dania na wynos:
2>&1 >/dev/null
jest! = >/dev/null 2>&1
. Jedno generuje wyjście, a drugie nie!Na koniec spójrz na te wspaniałe zasoby:
Dokumentacja Bash na temat przekierowań , objaśnienie tabel deskryptorów plików , wprowadzenie do wskaźników
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
Jest to powiązane: Pisanie stdOut i stderr do syslog.
To prawie działa, ale nie z Xinted; (
Chciałem, aby rozwiązaniem było zapisanie danych wyjściowych ze stdout plus stderr w pliku dziennika, a stderr wciąż na konsoli. Więc musiałem zduplikować wyjście stderr przez tee.
Oto rozwiązanie, które znalazłem:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
W sytuacji, gdy konieczne jest „orurowanie”, możesz użyć:
| I
Na przykład:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
lub
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
Te rozwiązania bazujące na bash mogą przesyłać STDOUT i STDERR osobno (od STDERR „sort -c” lub od STDERR do „sort-h”).
Poniższych funkcji można użyć do zautomatyzowania procesu przełączania wyjść między stdout / stderr i plikiem dziennika.
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
Przykład użycia w skrypcie:
echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs
echo "this goes to stdout"
@ fernando-fabreti
Dodając do tego, co zrobiłeś, nieznacznie zmieniłem funkcje i usunąłem & - zamknięcie i zadziałało to dla mnie.
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
W sytuacjach, gdy rozważasz użycie takich rzeczy exec 2>&1
, łatwiej jest odczytać, jeśli to możliwe, przepisywanie kodu za pomocą funkcji bash takich jak to:
function myfunc(){
[...]
}
myfunc &>mylog.log