перенаправить копирование стандартного вывода в файл журнала изнутри самого скрипта bash - PullRequest
219 голосов
/ 04 июля 2010

Я знаю, как перенаправить стандартный вывод в файл:

exec > foo.log
echo test

это поместит 'test' в файл foo.log.

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

т.е. это может быть сделано тривиально снаружи скрипта:

script | tee foo.log

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

Я пытался

exec | tee foo.log

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

Ответы [ 9 ]

280 голосов
/ 04 августа 2010
#!/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, используя цвета и колоночный вывод), обнаружатКонструкция выше означает, что они выводятся в канал.

Существуют опции для принудительного раскрашивания / колонирования (например, ls -C --color=always).Обратите внимание, что это приведет к тому, что коды цветов будут также записаны в файл журнала, что делает его менее читаемым.

163 голосов
/ 09 августа 2012

Принятый ответ не сохраняет 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

в начало сценария.)

Спецификация POSIX.1-2008 tee(1) требует, чтобы вывод был небуферизованным, то есть даже не буферизованным строкой, поэтому в этом случае возможно, что STDOUT и STDERR могут оказаться в одном и том же месте. строка foo.log; однако это также может произойти на терминале, поэтому файл журнала будет точным отражением того, что может быть видно на терминале, если не является точным отражением этого. Если вы хотите, чтобы строки STDOUT были четко отделены от строк STDERR, рассмотрите возможность использования двух файлов журнала, возможно, с префиксами отметок даты в каждой строке, чтобы впоследствии разрешить хронологическую сборку.

25 голосов
/ 05 марта 2011

Решение для busybox, macOS bash и non-bash shell

Принятый ответ, безусловно, лучший выбор для 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 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
18 голосов
/ 04 июля 2010

Внутри вашего скрипта поместите все команды в круглые скобки, например:

(
echo start
ls -l
echo end
) | tee foo.log
13 голосов
/ 07 декабря 2014

Простой способ сделать запись bash-скрипта в syslog. Вывод скрипта доступен как через /var/log/syslog, так и через stderr. Системный журнал добавит полезные метаданные, включая метки времени.

Добавьте эту строку вверху:

exec &> >(logger -t myscript -s)

Либо отправьте журнал в отдельный файл:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

Для этого требуется moreutils (для команды ts, которая добавляет временные метки).

9 голосов
/ 09 июля 2011

Используя принятый ответ, мой скрипт возвращался исключительно рано (сразу после 'exec>> (tee ...)'), оставляя остальную часть моего скрипта работающей в фоновом режиме. Поскольку я не мог заставить это решение работать по-своему, я нашел другое решение / решение проблемы:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

Это приводит к тому, что вывод скрипта идет из процесса через канал в суб-фоновый процесс 'tee', который записывает все на диск и в исходный стандартный вывод скрипта.

Обратите внимание, что 'exec &>' перенаправляет как stdout, так и stderr, мы можем перенаправить их отдельно, если захотим, или изменить на 'exec>', если нам просто нужен stdout

Даже если канал удален из файловой системы в начале сценария, он продолжит функционировать до завершения процессов. Мы просто не можем ссылаться на него, используя имя файла после строки rm.

1 голос
/ 04 июля 2010

Bash 4 имеет команду coproc, которая устанавливает именованный канал для команды и позволяет вам обмениваться данными через нее.

0 голосов
/ 27 июля 2018

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

0 голосов
/ 04 июля 2010

Ни один из них не является идеальным решением, но вот пара вещей, которые вы можете попробовать:

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 это потом в родительской оболочке).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...