Перенаправление stderr и stdout в Bash

Я хочу, чтобы перенаправить stdout и stderr в один файл. Как это сделать в Баш?

13 ответов


посмотреть здесь. Должно быть:

yourcommand &>filename

(перенаправляет как stdout и stderr к имени файла).


do_something 2>&1 | tee -a some_file

Это перенаправит stderr в stdout и stdout в some_file и печатать в stdout.


вы можете перенаправить stderr to stdout и stdout в файл:

some_command >file.log 2>&1 

см.http://tldp.org/LDP/abs/html/io-redirection.html

этот формат предпочтительнее, чем самый популярный &> формат, который работает только в bash. В Bourne shell Это можно интерпретировать как выполнение команды в фоновом режиме. Также формат более читаемый 2 (является STDERR) перенаправляется на 1 (STDOUT).

изменить: изменен порядок, как указано в комментариях


# 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'"

теперь простое эхо будет писать в $LOG_FILE. Полезно для daemonizing.

автору оригинального поста,

Это зависит от того, что вам нужно для достижения. Если вам просто нужно перенаправить команду, которую вы вызываете из своего скрипта, ответы уже даны. Мой о перенаправлении внутри текущий скрипт, который влияет на все команды / встроенные модули (включая вилки) после упомянутого фрагмента кода.


еще один крутой решение заключается в перенаправлении как на std-err / out, так и на logger или log-файл сразу, что включает в себя разделение "потока" на два. Эта функциональность обеспечивается командой "tee", которая может записывать/добавлять сразу несколько файловых дескрипторов(файлы, сокеты, трубы и т. д.): 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=""

    RETVAL=''
    local pids=`ps x -o pid,ppid | awk "\ == \"$ppid\" { print \ }"`
    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
}

Итак, с самого начала. Предположим, что у нас есть терминал, подключенный к /dev /stdout(FD #1) и/dev / stderr(FD #2). На практике это может быть труба, гнездо или что угодно.

  • создайте FDs #3 и #4 и укажите на то же "местоположение", что и #1 и #2 соответственно. Изменение FD #1 теперь не влияет на FD #3. Теперь FDs #3 и #4 указывают на STDOUT и STDERR соответственно. Они будут использоваться как реальная терминал STDOUT и STDERR.
  • 1> >(...) перенаправляет STDOUT на команду в parens
  • parens (sub-shell) выполняет чтение " tee "из STDOUT exec (pipe) и перенаправляет команду "logger" через другую труба к Под-раковине в parens. В то же время он копирует тот же вход в FD #3(терминал)
  • вторая часть, очень похожая, о том же трюке для STDERR и FDs #2 и #4.

результат запуска скрипта, имеющего вышеуказанную строку и дополнительно этот:

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

...выглядит следующим образом:

$ ./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

если вы хотите увидеть более четкое изображение, добавьте эти 2 строки в скрипт:

ls -l /proc/self/fd/
ps xf

bash your_script.sh 1>file.log 2>&1

1>file.log указывает оболочке отправить STDOUT в файл file.log и 2>&1 говорит ему перенаправить STDERR (файловый дескриптор 2) в STDOUT (файловый дескриптор 1).

Примечание: порядок имеет значение как liw.фай указал,2>&1 1>file.log не работает.


Любопытно, что это работает:

yourcommand &> filename

но это дает синтаксическую ошибку:

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

вы должны использовать:

yourcommand 1>> filename 2>&1

короткий ответ: Command >filename 2>&1 или Command &>filename


объяснение:

рассмотрим следующий код, который печатает слово " stdout "в stdout и слово" stderror " в stderror.

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

обратите внимание, что оператор ' & ' сообщает bash, что 2-это файловый дескриптор (который указывает на stderr), а не имя файла. Если мы опустим'&', эта команда будет печатать stdout в stdout, и создайте файл с именем "2" и пиши stderror там.

экспериментируя с кодом выше, вы можете сами увидеть, как работают операторы перенаправления. Например, изменив какой файл, какой из двух дескрипторов 1,2, перенаправляется на /dev/null следующие две строки кода удаляют все из stdout и все из stderror соответственно (печать того, что осталось).

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

теперь мы можем объяснить, почему решение, почему следующий код не производит вывод:

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

чтобы по-настоящему понять это, я настоятельно рекомендую вам прочитать этот веб-страница в таблицах дескрипторов файлов. Предполагая, что вы сделали это чтение, мы можем продолжить. Обратите внимание, что Bash обрабатывает слева направо; таким образом, Bash видит >/dev/null во-первых (что то же самое, что 1>/dev/null) и устанавливает файловый дескриптор 1 для указания на /dev/null вместо stdout. Сделав это, Баш затем перемещается вправо и видит 2>&1. Это устанавливает файловый дескриптор 2 в точку в тот же файл как файловый дескриптор 1 (а не сам файловый дескриптор 1!!!! (см. этот ресурс по указателям для получения дополнительной информации)) . Поскольку файловый дескриптор 1 указывает на /dev /null, а файловый дескриптор 2 указывает на тот же файл, что и файловый дескриптор 1, файловый дескриптор 2 теперь также указывает на/dev / null. Таким образом, оба дескриптора файлов указывают на /dev/null, и поэтому вывод не отображается.


чтобы проверить, действительно ли вы понимаете концепцию, попробуйте угадать вывод при переключении порядка перенаправления:

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

stderror

рассуждение здесь заключается в том, что, оценивая слева направо, Bash видит 2>&1 и, таким образом, устанавливает файловый дескриптор 2 в то же место, что и файловый дескриптор 1, т. е. stdout. Затем он устанавливает файловый дескриптор 1 (Помните, что >/dev/null = 1>/dev/null), чтобы указать на >/dev/null, тем самым удаляя все, что обычно отправляется в стандарт out. Таким образом, все, с чем мы остались, было то, что не было отправлено в stdout в подрешетке (код в скобках)- т. е. "stderror". Интересно отметить, что, хотя 1 является просто указателем на stdout, перенаправление указателя 2 на 1 через 2>&1 не образует цепочку указателей 2 - > 1 - > stdout. Если это так, то в результате перенаправления 1 в /dev / null код 2>&1 >/dev/null даст цепочку указателей 2 - > 1 - > /dev/null, и, таким образом, код не будет генерировать ничего, в отличие от того, что мы видели выше.


наконец, я бы отметил, что есть более простой способ сделать это:

из раздела 3.6.4 здесь, мы видим, что мы можем использовать оператор &> для перенаправления stdout и stderr. Таким образом, перенаправить вывод stderr и stdout любой команды на \dev\null (который удаляет вывод), мы просто вводим $ command &> /dev/null или в случае моего примера:

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

ключевые takeaways:

  • файловые дескрипторы ведите себя как указатели (хотя файловые дескрипторы не совпадают с указателями файлов)
  • перенаправление файлового дескриптора " a "в файловый дескриптор" b", который указывает на файл" f", заставляет файловый дескриптор" a "указывать на то же место, что и файловый дескриптор b - файл"f". Он не образует цепочку указателей a - > b - > f
  • из-за вышеизложенного порядок имеет значение,2>&1 >/dev/null есть != >/dev/null 2>&1. Один генерирует выход, а другой нет!

наконец посмотрите на эти большие ресурсы:

документация Bash по перенаправлению, объяснение таблиц дескрипторов файлов, введение в указатели


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" )

это связано: запись stdOut & stderr в системный журнал.

Это почти работает, но не от xinted ;(


Я хотел, чтобы решение имело вывод из stdout plus stderr, записанный в файл журнала, и stderr все еще на консоли. Поэтому мне нужно было дублировать вывод stderr через tee.

Это решение я нашел:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • первый обмен stderr и stdout
  • затем добавьте stdout в файл журнала
  • pipe stderr to tee и добавьте его также в файл журнала

"самый простой" способ (только bash4):ls * 2>&- 1>&-.


следующие функции могут использоваться для автоматизации процесса переключения выходов beetwen stdout / stderr и файла журнала.

#!/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:  => 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=
        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"
    }

пример использования скрипта:

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"

для tcsh, я должен использовать следующую команду :

command >& file

при использовании command &> file, это даст ошибку" недопустимая нулевая команда".


@fernando-fabreti

добавив к тому, что вы сделали, я немного изменил функции и удалил &- закрытие, и это сработало для меня.

    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:  => logfile to write to
  function redirectOutputsToLogfile {
      if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
        LOGFILE=
        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"