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