перенаправить копию вывода в лог-файл в bash-скрипт сам



Я знаю, как перенаправить stdout файл:



exec > foo.log
echo test


это поставит "тест" в foo.журнал.



Теперь я хочу перенаправить вывод в файл журнала и сохранить его на stdout



т. е. это можно сделать тривиально из-за пределов скрипт:



script | tee foo.log


но я хочу сделать объявить его в самом скрипте



пробовал



exec | tee foo.log


но это не сработало.

592   9  

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

    Ничего не найдено.