перенаправить копию вывода в лог-файл в bash-скрипт сам
Я знаю, как перенаправить stdout файл:
exec > foo.log
echo test
это поставит "тест" в foo.журнал.
Теперь я хочу перенаправить вывод в файл журнала и сохранить его на stdout
т. е. это можно сделать тривиально из-за пределов скрипт:
script | tee foo.log
но я хочу сделать объявить его в самом скрипте
пробовал
exec | tee foo.log
но это не сработало.
9 ответов:
#!/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. Если вы вызываете скрипт с помощьюsh myscript.sh, вы получите ошибку по строкамsyntax error near unexpected token '>'.если вы работаете с сигнальными ловушками, вы можете использовать
tee -iвозможность избежать нарушения работы выхода при возникновении сигнала. (Спасибо JamesThomasMoon1979 за комментарий.)
инструменты, которые изменяют свои выходные данные в зависимости от того, пишут ли они в канал или терминал (
lsиспользование цвета и columnized output, например) определит вышеуказанную конструкцию как означающую, что они выводятся в трубу.есть опции для принудительного раскрашивания / колумнизации (например,
ls -C --color=always). Обратите внимание, что это приведет к тому, что цветовые коды будут записаны в файл журнала, что сделает его меньше читаем.
принятый ответ не сохраняет STDERR как отдельный файловый дескриптор. Это значит
./script.sh >/dev/nullне выводит
barв терминал, только в лог-файл, и./script.sh 2>/dev/nullвыведет оба
fooиbarк терминалу. Ясно, что это не так поведение, которое, вероятно, ожидает обычный пользователь. Это может быть исправлено с помощью двух отдельных процессов tee, которые добавляются к одному и тому же файл журнала:#!/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в верхней части скрипта.)
The POSIX.Спецификация 1-2008 от
tee(1)требует, чтобы выходные данные не буферизовались, т. е. даже не буферизовались в строке, поэтому в этом случае возможно, что STDOUT и STDERR могут оказаться в одной строкеfoo.log, однако это также может произойти на терминал, поэтому файл журнала будет верным отражением того, что может можно увидеть на терминале, если не точное его зеркало. Если вы хотите, чтобы строки STDOUT были четко отделены от строк STDERR, рассмотрите возможность использования двух файлов журнала, возможно, с префиксами даты на каждой строке, чтобы разрешить хронологическую сборку позже.
решение для busybox и non-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's output PIPE=tmp.fifo mkfifo $PIPE # Launch the child process without redirected to the named pipe SELF_LOGGING=1 sh $* >$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 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
внутри файла сценария поместите все команды в скобки, например:
( echo start ls -l echo end ) | tee foo.log
простой способ сделать журнал сценария bash в системный журнал. Вывод скрипта доступен как через
/var/log/syslogи через stderr. системный журнал добавит полезные метаданные, включая временные метки.добавьте эту строку вверху:
exec &> >(logger -t myscript -s)кроме того, отправьте журнал в отдельный файл:
exec &> >(ts |tee -a /tmp/myscript.output >&2 )для этого требуется
moreutils(дляtsкоманда, которая добавляет метки времени).
используя принятый ответ мой скрипт продолжал возвращаться исключительно рано (сразу после ' exec > >(tee ...)') оставляя остальную часть моего скрипта, работающего в фоновом режиме. Поскольку я не мог заставить это решение работать по-своему, я нашел другое решение / обойти проблему:
# Logging setup logfile=mylogfile mkfifo ${logfile}.pipe tee < ${logfile}.pipe $logfile & exec &> ${logfile}.pipe rm ${logfile}.pipe # Rest of my scriptэто делает вывод из сценария перейти от процесса, через трубу в суб фоновый процесс "tee", который регистрирует все на диск и оригинальный stdout сценария.
Примечание. что 'exec & >' перенаправляет как stdout, так и stderr, мы могли бы перенаправить их отдельно, если хотим, или изменить на 'exec>', если мы просто хотим stdout.
даже если труба удалена из файловой системы в начале скрипта, она будет продолжать функционировать до завершения процессов. Мы просто не можем ссылаться на него, используя имя файла после rm-line.
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) $@ >> log.txt TEE=false $@ 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второй оставил бы файл трубы, сидящий вокруг, если что-то пойдет не так с вашим скриптом, что может быть или не быть проблемой (т. е., Возможно, вы могли бы
rmэто в родительской оболочке впоследствии).
Comments