Я знаю, как перенаправить stdout в файл:
exec > foo.log
echo test
это поместит "тест" в файл foo.log.
Теперь я хочу перенаправить вывод в файл журнала и сохранить его на stdout
то есть. это можно сделать тривиально извне script:
script | tee foo.log
но я хочу сделать объявление в самом script
Я пробовал
exec | tee foo.log
но это не сработало.
#!/usr/bin/env bash
# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)
# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1
echo "foo"
echo "bar" >&2
Обратите внимание, что это bash
, а не sh
. Если вы вызываете script с помощью sh myscript.sh
, вы получите сообщение об ошибке в строке syntax error near unexpected token '>'
.
Если вы работаете с сигнальными ловушками, вы можете использовать опцию tee -i
, чтобы избежать нарушения выхода, если происходит сигнал. (Спасибо JamesThomasMoon1979 за комментарий.)
Инструменты, которые изменяют свой вывод в зависимости от того, записывают ли они в трубу или терминал (например, ls
с использованием цветов и столбчатого вывода, например), обнаруживают вышеприведенную конструкцию, что означает, что они выводятся в канал.
Существуют опции для принудительного выполнения раскраски/столбцов (например, ls -C --color=always
). Обратите внимание, что это приведет к тому, что цветовые коды будут записываться в файл журнала, что сделает его менее читаемым.
Принятый ответ не сохраняет STDERR как отдельный файловый дескриптор. Это означает
./script.sh >/dev/null
не выводит bar
на терминал, только в файл журнала и
./script.sh 2>/dev/null
выводит на терминал как foo
, так и bar
. Ясно, что не
поведение, которое обычно ожидает обычный пользователь. Это может быть
фиксируется с использованием двух отдельных процессов тройника, которые добавляются к одному и тому же
файл журнала:
#!/bin/bash
# See (and upvote) the comment by JamesThomasMoon1979
# explaining the use of the -i option to tee.
exec > >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)
echo "foo"
echo "bar" >&2
(Обратите внимание, что вышеописанное не изначально обрезает файл журнала - если вы хотите, чтобы это поведение было добавлено
>foo.log
в верхней части script.)
Спецификация POSIX.1-2008 tee(1)
требует, чтобы выход был небуферизованным, то есть даже не буферизированным по строке, поэтому в этом случае возможно, что STDOUT и STDERR могут оказаться в одной строке foo.log
; однако это также может произойти на терминале, поэтому файл журнала будет точным отражением того, что можно увидеть на терминале, если не точное зеркало. Если вы хотите, чтобы строки STDOUT были чисто отделены от строк STDERR, рассмотрите возможность использования двух файлов журнала, возможно, с префиксами даты печати на каждой строке, чтобы впоследствии разрешить хронологическую сборку.
exec > >(tee -a $LOG)
trap "kill -9 $! 2>/dev/null" EXIT
exec 2> >(tee -a $LOG >&2)
trap "kill -9 $! 2>/dev/null" EXIT
-i
в tee
. В противном случае прерывания сигнала (ловушки) будут нарушать стандартный вывод в сценарии. Например, если вы trap 'echo foo' EXIT
а затем нажимаете ctrl+c
, вы не увидите « foo ». Поэтому я бы exec > >(tee -ia foo.log)
ответ на exec > >(tee -ia foo.log)
.
Решение для busybox, macOS bash и не-bash оболочек
Принятый ответ, безусловно, лучший выбор для Bash. Я работаю в среде Busybox без доступа к bash, и он не понимает синтаксис exec > >(tee log.txt)
. Он также не выполняет exec >$PIPE
должным образом, пытаясь создать обычный файл с тем же именем, что и именованный канал, который завершается ошибкой и зависает.
Надеюсь, это будет полезно для кого-то еще, у кого нет bash.
Кроме того, для любого, кто использует именованный канал, безопасно использовать rm $PIPE
, потому что это освобождает канал от VFS, но процессы, использующие его, по-прежнему поддерживают счетчик ссылок до тех пор, пока они не будут завершены.
Обратите внимание, что использование $ * не обязательно безопасно.
#!/bin/sh
if [ "$SELF_LOGGING" != "1" ]
then
# The parent process will enter this branch and set up logging
# Create a named piped for logging the child output
PIPE=tmp.fifo
mkfifo $PIPE
# Launch the child process with stdout redirected to the named pipe
SELF_LOGGING=1 sh $0 $* >$PIPE &
# Save PID of child process
PID=$!
# Launch tee in a separate process
tee logfile <$PIPE &
# Unlink $PIPE because the parent process no longer needs it
rm $PIPE
# Wait for child process, which is running the rest of this script
wait $PID
# Return the error code from the child process
exit $?
fi
# The rest of the script goes here
Внутри вашего файла script поместите все команды в круглые скобки, например:
(
echo start
ls -l
echo end
) | tee foo.log
{}
)
Простой способ сделать журнал bash script для syslog. Выход script доступен как через /var/log/syslog
, так и через stderr. syslog добавит полезные метаданные, включая отметки времени.
Добавьте эту строку вверху:
exec &> >(logger -t myscript -s)
В качестве альтернативы отправьте журнал в отдельный файл:
exec &> >(ts |tee -a /tmp/myscript.output >&2 )
Для этого требуется moreutils
(для команды ts
, которая добавляет временные метки).
Используя принятый ответ, мой script возвращался исключительно рано (сразу после "exec → (tee...)" ), оставив остальную часть моего script в фоновом режиме. Поскольку я не мог получить это решение для работы, я нашел другое решение/работа с проблемой:
# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe
# Rest of my script
Это делает вывод из script переходом из процесса через трубку в вспомогательный фоновый процесс 'tee', который записывает все на диск и в исходное stdout script.
Обратите внимание, что 'exec & > ' перенаправляет как stdout, так и stderr, мы могли бы перенаправить их по отдельности, если хотите, или изменить на 'exec > ', если мы просто хотим stdout.
Даже если вы удалите трубку из файловой системы в начале script, она будет продолжать функционировать до тех пор, пока процессы не закончатся. Мы просто не можем ссылаться на него, используя имя файла после строки rm.
$logfile
часть tee < ${logfile}.pipe $logfile &
. В частности, я попытался изменить это, чтобы перехватить полностью развернутые строки журнала команд (из set -x
) в файл, показывая только строки без начального «+» в stdout, изменив на (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &
но получил сообщение об ошибке, касающееся $logfile
. Можете ли вы объяснить tee
чуть более подробно?
Bash 4 имеет команду coproc
, которая устанавливает именованный канал в команду и позволяет вам обмениваться данными.
Не могу сказать, что мне нравится любое решение на основе exec. Я предпочитаю использовать tee напрямую, поэтому я делаю скрипт, вызывающий себя с tee по запросу:
# my script:
check_tee_output()
{
# copy (append) stdout and stderr to log file if TEE is unset or true
if [[ -z $TEE || "$TEE" == true ]]; then
echo '-------------------------------------------' >> log.txt
echo '***' $(date) $0 $@ >> log.txt
TEE=false $0 $@ 2>&1 | tee --append log.txt
exit $?
fi
}
check_tee_output $@
rest of my script
Это позволяет сделать это:
your_script.sh args # tee
TEE=true your_script.sh args # tee
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args # tee
Вы можете настроить это, например, сделать tee = false по умолчанию вместо этого, сделать TEE, удерживая файл журнала, и т.д. Я предполагаю, что это решение похоже на jbarlow, но более простое, возможно, у меня есть ограничения, которые я еще не встречал.
Ни одно из них не является идеальным решением, но вот несколько вещей, которые вы могли бы попробовать:
exec >foo.log
tail -f foo.log &
# rest of your script
или
PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE
Во-вторых, если файл script не работает с файлом, который может или не может быть проблемой (т.е. возможно, вы могли бы rm
его в родительской оболочке впоследствии).
tee
- Я редактировал. Как я уже сказал, ни одно из них не является идеальным решением, но фоновые процессы будут убиты, когда их родительская оболочка завершится, так что вам не придется беспокоиться о том, что они будут затягивать ресурсы вечно.