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

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

вопрос задан 12.03.2009
flybywire
91163 репутация

13 ответов


  • 671 рейтинг

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

    yourcommand &>filename
    

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

    ответ дан dirkgently, с репутацией 88667, 12.03.2009
  • 401 рейтинг
    do_something 2>&1 | tee -a some_file
    

    Это перенаправит stderr на стандартный вывод и стандартный вывод на some_file, и напечатает его на стандартный вывод.

    ответ дан Marko, с репутацией 21419, 12.03.2009
  • 206 рейтинг

    Можно перенаправить стандартный поток 3230269583 stderr в стандартный поток 3230269583 и стандартный поток в файл:

    some_command >file.log 2>&1 
    

    См. http: // tldp. орг / ЛДП / ABS / HTML / IO-перенаправление. HTML

    Этот формат предпочтительнее, чем самый популярный & amp; & gt; формат, который работает только в Bash. В оболочке Bourne это можно интерпретировать как запуск команды в фоновом режиме. Кроме того, формат более читабелен 2 (STDERR) перенаправлен на 1 (STDOUT).

    РЕДАКТИРОВАТЬ: изменил порядок, как указано в комментариях

    ответ дан f3lix, с репутацией 23330, 12.03.2009
  • 173 рейтинг
    # 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. Полезно для демонизации.

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

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


    Еще одно крутое решение - это перенаправление одновременно на std-err / out и на логгер или файл журнала, что включает в себя разделение «потока» на две части. Эта функциональность обеспечивается командой 'tee', которая может записывать / добавлять несколько дескрипторов файлов (файлы, сокеты, каналы и т. Д.) Одновременно: tee FILE1 FILE2. , , & gt; (cmd1) & gt; (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). На практике это может быть труба, розетка или что-то еще.

    • Создайте FD # 3 и # 4 и укажите на то же «местоположение», что и # 1 и # 2 соответственно. Смена FD # 1 с сегодняшнего дня не влияет на FD # 3. Теперь FDs # 3 и # 4 указывают на STDOUT и STDERR соответственно. Они будут использоваться в качестве реального терминала STDOUT и STDERR.
    • 1 & gt; & GT; (. , , ) перенаправляет STDOUT на команду в parens
    • parens (sub-shell) выполняет чтение 'tee' из STDOUT (pipe) exec и перенаправляет на команду 'logger' через другой канал в sub-shell в 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
    
    ответ дан quizac, с репутацией 1840, 13.12.2013
  • 35 рейтинг
    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 не работает.

    ответ дан Guðmundur H, с репутацией 6464, 12.03.2009
  • 20 рейтинг

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

    yourcommand &> filename
    

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

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

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

    yourcommand 1>> filename 2>&1
    
    ответ дан does_not_exist, с репутацией , 6.05.2009
  • 10 рейтинг

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


    Объяснение:

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

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

    Обратите внимание, что & amp; оператор сообщает bash, что 2 - это дескриптор файла (который указывает на stderr), а не имя файла. Если мы пропустим '& amp;', эта команда выведет 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. Это устанавливает дескриптор файла 3 так, чтобы он указывал на тот же файл , что и дескриптор файла 1 (а не на сам дескриптор файла 1! ! ! ! (см. этот ресурс по указателям для получения дополнительной информации)). Поскольку файловый дескриптор 1 указывает на / dev / null, а файловый дескриптор 2 указывает на тот же файл, что и файловый дескриптор 1, файловый дескриптор 2 теперь также указывает на / dev / null. Таким образом, оба файловых дескриптора указывают на / dev / null, и поэтому вывод не производится.


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

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

    stderror

    Причиной здесь является то, что при оценке слева направо Bash видит 2 & amp; 1 и таким образом устанавливает дескриптор файла 2 так, чтобы он указывал на то же место, что и дескриптор файла 1, то есть stdout. Затем он устанавливает файловый дескриптор 1 (помните, что & gt; / dev / null = 1 / g /; dev / null), чтобы он указывал на / dev / null, удаляя, таким образом, все, что обычно отправляется стандарту. Таким образом, все, что нам осталось, это то, что не было отправлено на стандартный вывод в подоболочке (код в скобках) - i. е. "Stderror". Интересно отметить, что хотя 1 является просто указателем на стандартный вывод, перенаправление указателя 2 на 1 через 2>&1 НЕ образует цепочку указателей 2 - & gt; 1 - & gt; стандартный вывод. Если это произойдет, в результате перенаправления 1 в / dev / null код 2>&1 >/dev/null даст цепочку указателей 2 - & gt; 1 - & gt; / 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 - & gt; б - & gt; е
    • Из-за вышеизложенного порядок имеет значение 2>&1 >/dev/null! = >/dev/null 2>&1. Один генерирует вывод, а другой нет!

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

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

    ответ дан Evan Rosica, с репутацией 328, 19.11.2017
  • 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 & amp; Стдерр в системный журнал.

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

    ответ дан does_not_exist, с репутацией , 23.04.2009
  • 5 рейтинг

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

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

    command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
    
    • Первый обмен stderr и stdout
    • затем добавьте стандартный вывод в файл журнала
    • труба stderr для тройника и добавить его также в файл журнала
    ответ дан bebbo, с репутацией 1667, 19.01.2017
  • 1 рейтинг

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

    ответ дан reim, с репутацией 153, 4.02.2016
  • 1 рейтинг

    Следующие функции могут быть использованы для автоматизации процесса переключения выходов между 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"
    
    ответ дан Fernando Fabreti, с репутацией 2815, 26.07.2018
  • 0 рейтинг

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

    command >& file
    

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

    ответ дан einstein6, с репутацией 15, 23.04.2013
  • 0 рейтинг

    @ fernando-fabreti

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

        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"
    
    ответ дан thom schumacher, с репутацией 677, 14.09.2018