Перенаправить stderr и stdout в Bash

576

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

  • 1
    Я хотел бы сказать, что это удивительно полезный вопрос. Многие люди не знают, как это сделать, поскольку им не приходится делать это часто, и это не лучшее документированное поведение Bash.
  • 1
    Иногда полезно просмотреть выходные данные (как обычно) и перенаправить их в файл. Смотрите ответ Марко ниже. (Я говорю это здесь, потому что легко посмотреть на первый принятый ответ, если этого достаточно для решения проблемы, но другие ответы часто предоставляют полезную информацию.)
Теги:
redirect
pipe

13 ответов

640
Лучший ответ

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

yourcommand &>filename

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

  • 25
    Этот синтаксис устарел в соответствии с Bash Hackers Wiki . Это?
  • 19
    Согласно wiki.bash-hackers.org/scripting/obsolete , он устарел в том смысле, что он не является частью POSIX, но страница руководства по bash не упоминает об удалении его из bash в ближайшем будущем. Страница man указывает предпочтение '&>' перед '> &', что в противном случае эквивалентно.
Показать ещё 7 комментариев
401
do_something 2>&1 | tee -a some_file

Это приведет к перенаправлению stderr на stdout и stdout на some_file и на печать в стандартный вывод.

  • 11
    В AIX (ksh) ваше решение работает. Принятый ответ do_something &>filename не имеет. +1.
  • 10
    @Daniel, но этот вопрос конкретно о Bash
Показать ещё 5 комментариев
196

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

some_command >file.log 2>&1 

См. Http://tldp.org/LDP/abs/html/io-redirection.html.

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

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

  • 39
    Это перенаправляет stderr на исходный stdout, а не в файл, куда идет stdout. Поставьте '2> & 1' после '> file.log', и это работает.
  • 1
    В чем преимущество этого подхода перед some_command &> file.log?
Показать ещё 6 комментариев
181
# 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. Полезно для демонализации.

Автору оригинального сообщения

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


Еще одно интересное решение - перенаправление на std-err/out AND на 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="$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
}

Итак, с самого начала. Предположим, что терминал подключен к /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" чтение из exec STDOUT (pipe) и перенаправляет команду "logger" через другой канал в суб-оболочку в parens. В то же время он копирует один и тот же вход в FD # 3 (терминал)
  • вторая часть, очень похожая, касается того же трюка для STDERR и FDs # 2 и # 4.

Результат работы script, имеющий указанную выше строку, и дополнительно:

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 строки в script:

ls -l /proc/self/fd/
ps xf
  • 1
    только одно исключение. в первом примере вы написали: exec 1 <> $ LOG_FILE. это потому, что оригинальный лог-файл всегда написан на более высоком уровне. для реального входа в журнал лучше: exec 1 >> $ LOG_FILE, потому что журнал всегда добавляется.
  • 3
    Это правда, хотя это зависит от намерений. Мой подход заключается в том, чтобы всегда создавать уникальный файл журнала с метками времени. Другой должен добавить. Оба способа являются «логотатабельными». Я предпочитаю отдельные файлы, которые требуют меньше разбора, но, как я уже сказал, все, что делает вашу лодку плавающей :)
Показать ещё 6 комментариев
34
bash your_script.sh 1>file.log 2>&1

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

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

19

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

yourcommand &> filename

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

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

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

yourcommand 1>> filename 2>&1
  • 9
    &>> похоже работает на BASH 4: $ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
10

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


Объяснение:

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

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

Обратите внимание, что оператор '&' сообщает bash, что 2 является файловым дескриптором (который указывает на stderr), а не именем файла. Если мы stderror "&", эта команда будет печатать 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. Сделав это, Bash затем движется вправо и видит 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, тем самым удалив все, что обычно будет отправлено на стандартный выход. Таким образом, все, что нам осталось, это то, что не было отправлено в 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

Ключевые вынос:

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

Наконец, взгляните на эти большие ресурсы:

Bash Documentation for Redirection, Объяснение таблиц дескрипторов файлов, Введение в указатели

  • 0
    Файловые дескрипторы (0, 1, 2) просто смещаются в таблицу. Когда используются 2> & 1, эффект - это слот FD [2] = dup (1), поэтому, куда бы FD [1] указывал, FD [2] теперь указывает на. Когда вы изменяете FD [1] на / dev / null, то FD [1] изменяется, но это не меняет слот FD [2] (который указывает на стандартный вывод). Я использую термин dup (), потому что это системный вызов, который используется для дублирования дескриптора файла.
8
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 в syslog.

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

  • 0
    Я предполагаю, что это не работает из-за "/ dev / fd / 3 Отказано в доступе". Переключение на> & 3 может помочь.
5

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

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

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • Первый swap stderr и stdout
  • затем добавьте stdout в файл журнала
  • pipe stderr to tee и добавьте его также в файл журнала
  • 0
    Кстати, это не сработало для меня (лог-файл пуст). | тройник не имеет никакого эффекта. Вместо этого я начал работать с помощью stackoverflow.com/questions/692000/…
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: $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"
    }

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

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"
  • 0
    когда я использую ваши функции, и он пытается восстановить стандартные выходные данные, я получаю эхо: ошибка записи: неверный номер файла, перенаправление работает отлично ... восстановление, кажется, не
  • 0
    чтобы заставить ваш скрипт работать, мне пришлось закомментировать эти строки, и я изменил порядок: #exec 1> & - #closes FD 1 (файл журнала) #exec 2> & - #closes FD 2 (файл журнала); exec 1> & 3 #restore stdout exec 2> & 4 #restore stderr
Показать ещё 2 комментария
1

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

0

@Фернанду-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: $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

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

command >& file

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню