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

679

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

ЭЛЕКТРОДИСТАНЦИОННАЯ СИСТЕМА УПРАВЛЕНИЯ
источник
2
Я хотел бы сказать, что это удивительно полезный вопрос. Многие люди не знают, как это сделать, поскольку им не приходится делать это часто, и это не лучшее документированное поведение Bash.
Роберт Вм Рудисуэли,
2
Иногда полезно просмотреть выходные данные (как обычно) и перенаправить их в файл. Смотрите ответ Марко ниже. (Я говорю это здесь, потому что легко посмотреть на первый принятый ответ, если этого достаточно для решения проблемы, но другие ответы часто предоставляют полезную информацию.)
jvriesem

Ответы:

763

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

yourcommand &>filename

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

dirkgently
источник
29
Этот синтаксис устарел в соответствии с Bash Hackers Wiki . Это?
Салман фон Аббас
20
Согласно wiki.bash-hackers.org/scripting/obsolete , он выглядит устаревшим в том смысле, что он не является частью POSIX, но страница руководства по bash не упоминает об удалении его из bash в ближайшем будущем. Страница man указывает предпочтение '&>' перед '> &', что в противном случае эквивалентно.
Чепнер
13
Я предполагаю, что мы не должны использовать &>, поскольку это не в POSIX, и обычные оболочки, такие как "dash", не поддерживают это.
Сэм Уоткинс
27
Дополнительный совет: если вы используете это в скрипте, убедитесь, что он начинается с, #!/bin/bashа не с #!/bin/sh, так как в требует bash.
Тор Клингберг
8
Или & >> добавить вместо перезаписи.
Александр Гончий
449
do_something 2>&1 | tee -a some_file

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

Marko
источник
16
В AIX (ksh) ваше решение работает. Принятый ответ do_something &>filenameне. +1.
Удержан
11
@Daniel, но этот вопрос конкретно о bash
Джон Ла Рой
3
Я понимаю, Ambiguous output redirect.почему?
Александр Холден Дейли
1
У меня есть скрипт ruby ​​(который я не хочу изменять), который печатает сообщения об ошибках жирным красным цветом. Этот скрипт ruby ​​вызывается из моего скрипта bash (который я могу изменить). Когда я использую вышеупомянутое, он печатает сообщения об ошибках в виде простого текста, минус форматирование. Есть ли способ сохранить форматирование на экране и получить вывод (и stdout, и stderr) в файл?
Атлантида
8
Обратите внимание, что (по умолчанию) это имеет побочный эффект, который $?больше не относится к состоянию выхода do_something, а к состоянию выхода tee.
Flimm
255

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

some_command >file.log 2>&1 

Видеть Http://tldp.org/LDP/abs/html/io-redirection.html.

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

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

f3lix
источник
47
Это перенаправляет stderr в исходный stdout, а не в файл, куда идет stdout. Поставьте '2> & 1' после '> file.log' и все заработает.
1
В чем преимущество этого подхода перед some_command &> file.log?
Ubermonkey
6
Если вы хотите добавить файл, вы должны сделать это следующим образом: echo "foo" 2> & 1 1 >> bar.txt AFAIK Нет способа добавить, используя &>
SlappyTheFish
9
Ага, извините, эхо "foo" 1 >> bar.txt 2> & 1
SlappyTheFish
11
Я думаю, что интерпретация, что 2> & 1 перенаправляет stderr в stdout, неверна; Я полагаю, что точнее будет сказать, что он отправляет stderr в то же самое место, куда идет stdout в данный момент времени. Таким образом, место 2> & 1 после первого перенаправления является существенным.
JDG
201
# 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 и на 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). На практике это может быть труба, розетка или что-то еще.

  • Создайте FD # 3 и # 4 и укажите на то же «местоположение», что и # 1 и # 2 соответственно. Смена FD # 1 с сегодняшнего дня не влияет на FD # 3. Теперь FDs # 3 и # 4 указывают на STDOUT и STDERR соответственно. Они будут использованы как реальные терминала STDOUT и STDERR.
  • 1>> (...) перенаправляет STDOUT для команды в паренсе
  • parens (sub-shell) выполняет чтение 'tee' из STDOUT (pipe) exec и перенаправляет на команду 'logger' через другой канал в sub-shell в parens. В то же время он копирует тот же вход в FD # 3 (терминал)
  • вторая часть, очень похожая, посвящена тому же трюку для STDERR и FD № 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
источник
1
только одно исключение. в первом примере вы написали: exec 1 <> $ LOG_FILE. это потому, что оригинальный лог-файл всегда написан на более высоком уровне. для реального входа в журнал лучше: exec 1 >> $ LOG_FILE, потому что журнал всегда добавляется.
Зник
4
Это правда, хотя это зависит от намерений. Мой подход заключается в том, чтобы всегда создавать уникальный файл журнала с метками времени. Другой должен добавить. Оба способа являются «логотатируемыми». Я предпочитаю отдельные файлы, которые требуют меньше разбора, но, как я уже сказал, все, что делает вашу лодку плавающей :)
викторина
1
Ваше второе решение информативно, но что со всем кодом очистки? Это не актуально, и если да, то только запутывает другой хороший пример. Я также хотел бы видеть, что он немного переработан, так что FD 1 и 2 не перенаправляются на регистратор, а 3 и 4, так что все, что вызывает этот сценарий, может манипулировать 1 и 2 в соответствии с общим предположением stdout == 1 и stderr == 2, но мои краткие эксперименты показывают, что это более сложно.
JFlo
1
Мне больше нравится с кодом очистки. Это может немного отвлечь от основного примера, но удаление его сделает пример неполным. Сеть уже полна примеров без обработки ошибок, или, по крайней мере, дружеское замечание, что ей все еще нужно около ста строк кода, чтобы сделать его безопасным для использования.
Золтан К.
1
Я хотел уточнить код очистки. Это часть скрипта, которая демонизирует эрго и становится невосприимчивой к сигналу HANG-UP. 'tee' и 'logger' - это процессы, порожденные одним и тем же PPID, и они наследуют ловушку HUP от основного сценария bash. Итак, когда основной процесс умирает, он наследуется init [1]. Они не станут зомби (defunc). Код очистки гарантирует, что все фоновые задачи будут убиты, если основной сценарий умирает. Это также относится к любому другому процессу, который мог быть создан и запущен в фоновом режиме.
викторина
41
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не работает.

Гудмундур Н
источник
21

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

yourcommand &> filename

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

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

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

yourcommand 1>> filename 2>&1

источник
10
&>>Кажется, работает на BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
user272735
15

Краткий ответ: 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. Сделав это, 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 является просто указателем на стандартный вывод, перенаправление указателя 2 на 1 через 2>&1НЕ образует цепочку указателей 2 -> 1 -> стандартный вывод. Если это произошло, в результате перенаправления 1 в / dev / null код2>&1 >/dev/null выдаст цепочку указателей 2 -> 1 -> / dev / null, и, следовательно, код не сгенерирует ничего, в отличие от того, что мы видели выше.


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

Из раздела 3.6.4 здесь мы видим , что мы можем использовать оператор &>перенаправления как стандартный вывод и стандартный поток ошибок. Таким образом, чтобы перенаправить вывод 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это! = >/dev/null 2>&1. Один генерирует вывод, а другой нет!

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

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

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


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

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

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

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • Первый обмен stderr и stdout
  • затем добавьте стандартный вывод в файл журнала
  • труба stderr, чтобы тройник и добавить его также в файл журнала
bebbo
источник
Кстати, это не сработало для меня (лог-файл пуст). | тройник не имеет никакого эффекта. Вместо этого я получил его с помощью stackoverflow.com/questions/692000/…
Ярослав Булатов
4

Для ситуации, когда необходим «трубопровод», вы можете использовать:

| &

Например:

echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt

или

TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log  ; done |& sort -h

Эти решения на основе bash могут передавать STDOUT и STDERR по отдельности (из STDERR из «sort -c» или из STDERR в «sort -h»).

dmitry_podyachev
источник
1

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

Reim
источник
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"
Фернандо Фабрети
источник
когда я использую ваши функции, и он пытается восстановить стандартные выходные данные, я получаю эхо: ошибка записи:
неверный
чтобы заставить ваш скрипт работать, мне пришлось закомментировать эти строки и изменить порядок: #exec 1> & - #closes FD 1 (файл журнала) #exec 2> & - #closes FD 2 (файл журнала); exec 1> & 3 #restore stdout exec 2> & 4 #restore stderr
thom schumacher
Жаль это слышать. Я не получаю никаких ошибок при работе в CentOS 7, Bash 4.2.46. Я аннотировал ссылку, где я получил эти команды. Это: Ссылка: logan.tw/posts/2016/02/20/open-and-close-files-in-bash
Фернандо Фабрети,
Я запускаю эти команды в AIX, возможно, поэтому. Я добавил пост для исправления, которое я сделал.
Том Шумахер
1

@ Фернанду-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"
Том Шумахер
источник
1

В ситуациях, когда вы рассматриваете возможность использования таких вещей, как exec 2>&1я, легче читать, если это возможно, переписывать код, используя функции bash, например:

function myfunc(){
  [...]
}

myfunc &>mylog.log
user2761220
источник
0

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

command >& file

При использовании command &> fileон выдаст ошибку «Invalid null command».

einstein6
источник