Przekierowanie stderr i stdout w Bash

Chcę przekierować zarówno stdout, jak i stderr procesu do jednego pliku. Jak to zrobić w Bash?

Author: codeforester, 2009-03-12

13 answers

Spójrz tutaj . Powinno być:

yourcommand &>filename

(przekierowuje zarówno stdout jak i stderr do nazwy pliku).

 673
Author: dirkgently,
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
2010-10-07 05:44:01
do_something 2>&1 | tee -a some_file

To przekieruje stderr na stdout i stdout na some_file i wydrukuj go na stdout.

 401
Author: Marko,
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
2015-10-27 10:33:02

Możesz przekierować stderr do stdout i 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 zinterpretować jako uruchamianie polecenia w tle. Również format jest bardziej czytelny 2 (jest STDERR) przekierowany do 1 (STDOUT).

EDIT: zmieniono kolejność wskazaną w komentarze

 206
Author: f3lix,
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
2018-05-11 04:54:54
# 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 do $LOG_FILE. Przydatne do demonizacji.

Do autora oryginalnego postu,

To zależy, co musisz osiągnąć. Jeśli potrzebujesz tylko przekierować w / z polecenia wywołanego ze skryptu, odpowiedzi są już podane. Mój dotyczy przekierowania wewnątrz bieżącego skryptu, który wpływa na wszystkie polecenia / wbudowane (zawiera forki) po wspomnianym fragmencie kodu.

Kolejnym fajnym rozwiązaniem jest przekierowanie do obu std-err/out I do loggera lub pliku dziennika naraz, co wiąże się z podzieleniem "strumienia" na dwa. Ta funkcjonalność jest dostarczana przez polecenie 'tee', które może zapisywać / dołączać do kilku deskryptorów plików (plików, gniazd, rur, itp.) jednocześnie: 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
}
Od 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.
  • Stwórz FDs #3 i # 4 i punkt do tej samej "lokalizacji" co odpowiednio #1 i #2. Zmiana FD # 1 nie wpływa na FD #3 od teraz. Teraz FDs # 3 i #4 wskazują odpowiednio na STDOUT i STDERR. Będą one używane jako real terminal STDOUT i STDERR.
  • 1> >(...) przekierowuje STDOUT do polecenia w parens
  • parens(sub-shell) wykonuje odczyt 'tee' ze STDOUT (pipe) exec 'a i przekierowuje do polecenia' logger ' poprzez inny pipe do sub-shell w parens. W tym samym czasie kopiuje to samo wejście do FD #3 (terminal)
  • druga część, bardzo podobna, jest o zrobieniu tej samej sztuczki dla STDERR i FDs #2 i # 4.

Wynik uruchomienia skryptu o powyższej linii i dodatkowo tej:

echo "Will end up in STDOUT(terminal) and /var/log/messages"

... jest następujące:

$ ./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 linie do skryptu:

ls -l /proc/self/fd/
ps xf
 173
Author: quizac,
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-10-27 16:58:10
bash your_script.sh 1>file.log 2>&1

1>file.log poleca powłoce wysłanie STDOUT do pliku file.log, a 2>&1 każe przekierować STDERR (deskryptor pliku 2) do STDOUT (deskryptor pliku 1).

Uwaga: kolejność ma znaczenie jako liw.fi 2>&1 1>file.log nie działa.

 35
Author: Guðmundur H,
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-12 09:33:48

Co ciekawe, to działa:

yourcommand &> filename

Ale to daje błąd składni:

yourcommand &>> filename
syntax error near unexpected token `>'

Musisz użyć:

yourcommand 1>> filename 2>&1
 20
Author: ,
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-05-06 14:12:30

Krótka odpowiedź: Command >filename 2>&1 lub Command &>filename


Wyjaśnienie:

Rozważ następujący kod, który wyświetla słowo " stdout "na stdout i słowo" stderror " na stderror.

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror

Zauważ, że operator ' & ' mówi bashowi, że 2 jest deskryptorem pliku (który wskazuje na stderr), a nie nazwą pliku. Jeśli pominęliśmy'&', polecenie to wyświetli stdout na stdout, utworzy plik o nazwie " 2 " i tam zapisze stderror.

Eksperymentując z powyższym kodem, możesz możesz zobaczyć na własne oczy, jak działają operatorzy przekierowań. Na przykład, zmieniając plik, który z dwóch deskryptorów 1,2 zostanie przekierowany do /dev/null, następujące dwie linie kodu usuwają odpowiednio wszystko ze stdout i wszystko ze stderror (wypisując to, co pozostało).

$ (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 wyjścia:

(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1

Aby to naprawdę zrozumieć, Gorąco polecam przeczytać tę stronę w pliku tabele deskryptorów . Zakładając, że to czytałeś, możemy kontynuować. Zauważ, że Bash przetwarza od lewej do prawej; dlatego Bash widzi najpierw >/dev/null (co jest takie samo jak 1>/dev/null) i ustawia deskryptor pliku 1, aby wskazywał na /dev / null zamiast na standardowe wyjście. Po wykonaniu tego zadania Bash porusza się w prawo i widzi 2>&1. To ustawia deskryptor pliku 2 , aby wskazywał na ten sam plik jako deskryptor pliku 1 (a nie na deskryptor pliku 1!!!! (zobacz ten zasób na wskaźnikach aby uzyskać więcej info)). Ponieważ deskryptor pliku 1 wskazuje na /dev /null, A deskryptor pliku 2 wskazuje na ten sam plik co deskryptor pliku 1, deskryptor pliku 2 wskazuje teraz również na/dev / null. Tak więc 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 odgadnąć wyjście, gdy zmienimy kolejność przekierowań:

(echo "stdout"; echo "stderror" >&2)  2>&1 >/dev/null

Stderror

Rozumowanie tutaj jest takie, że ocena od lewej do tak, Bash widzi 2> & 1, a więc ustawia deskryptor pliku 2 tak, aby wskazywał na to samo miejsce, co deskryptor pliku 1, czyli stdout. Następnie ustawia deskryptor pliku 1 (pamiętaj, że >/dev/null = 1 > /dev / null), aby wskazywał na >/dev/null, usuwając w ten sposób wszystko, co zwykle zostanie wysłane do standardowego wyjścia. Pozostaje nam więc tylko to, co nie zostało wysłane na stdout w podshell (kod w nawiasach) - tzn. "stderror". Ciekawostką jest to, że mimo że 1 jest tylko wskaźnikiem na stdout przekierowanie wskaźnika 2 do 1 przez 2>&1 nie tworzy łańcucha wskaźników 2 - > 1 - > stdout. Gdyby tak się stało, w wyniku przekierowania 1 do /dev/null, kod 2>&1 >/dev/null dałby łańcuch wskaźników 2 -> 1 - > /dev / null, a tym samym kod nie generowałby niczego, w przeciwieństwie do tego, co widzieliśmy powyżej.


Na koniec chciałbym zauważyć, że jest prostszy sposób, aby to zrobić:

Z sekcji 3.6.4 tutaj widzimy, że możemy użyć operatora &> do przekierowania obu stdout i stderr. Tak więc, aby przekierować zarówno wyjście stderr jak i stdout dowolnego polecenia do \dev\null (które usuwa wyjście), po prostu wpisujemy $ command &> /dev/null lub w przypadku mojego przykładu:

$ (echo "stdout"; echo "stderror" >&2) &>/dev/null

Kluczowe terminy:

  • deskryptory plików zachowują się jak wskaźniki (chociaż deskryptory plików nie są takie same jak wskaźniki plików)
  • przekierowanie deskryptora pliku " a "na deskryptor pliku "b", który wskazuje na plik "f", powoduje, że deskryptor pliku "a" wskazuje na to samo miejsce co deskryptor pliku b-plik "f". Nie tworzy łańcucha wskaźników a -> b - > f
  • Z powodu powyższego, porządek ma znaczenie, 2>&1 >/dev/null jest != >/dev/null 2>&1. Jeden generuje wyjście, a drugi nie!

W końcu rzućcie okiem na te wspaniałe zasoby:]}

Dokumentacja Bash dotycząca przekierowań, Wyjaśnienie tabel deskryptorów plików, Wprowadzenie do wskaźników

 10
Author: Evan Rosica,
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
2018-04-25 21:37:05
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 związane: zapisywanie stdOut & stderr do syslog.

To prawie działa, ale nie z xinted ; (

 8
Author: ,
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-04-23 13:14:35

Chciałem, aby wyjście ze stdout plus stderr było zapisane w pliku dziennika, a stderr nadal na konsoli. Musiałem więc 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
  • Pierwsza Zamiana stderr i stdout
  • następnie dołącz stdout do pliku dziennika
  • przeciągnij stderr do tee i dodaj go również do pliku dziennika
 5
Author: bebbo,
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-01-19 19:21:18

"najprostszy" sposób (tylko bash4): ls * 2>&- 1>&-.

 1
Author: reim,
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
2016-05-31 08:44:42

Następujące funkcje mogą być użyte do automatyzacji procesu przełączania wyjść beetwen stdout/stderr i pliku 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 wewnątrz skryptu:

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"
 1
Author: Fernando Fabreti,
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
2018-07-26 20:49:40

Dla tcsh, muszę użyć następującego polecenia:

command >& file

Jeśli użyjesz command &> file, wyświetli błąd "Invalid null command".

 0
Author: einstein6,
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-04-23 05:07:32

@fernando-fabreti

Dodając do tego, co zrobiłeś, zmieniłem nieco funkcje i usunąłem & - zamknięcie i działało 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"
 0
Author: thom schumacher,
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
2018-09-14 16:35:13