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

214

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

exec > foo.log
echo test

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

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

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

script | tee foo.log

но я хочу сделать объявление в самом script

Я пробовал

exec | tee foo.log

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

  • 2
    Ваш вопрос плохо сформулирован. Когда вы вызываете 'exec> foo.log', выводом скрипта является файл foo.log. Я думаю , что вы имеете в виду , что вы хотите , чтобы вывод пойти в foo.log и на терминал, так как собирается foo.log собирается на стандартный вывод.
  • 0
    что я хотел бы сделать, это использовать | на "Exec". это было бы идеально для меня, то есть "exec | tee foo.log", к сожалению, вы не можете использовать перенаправление канала при вызове exec
Теги:
redirect

9 ответов

283
Лучший ответ
#!/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). Обратите внимание, что это приведет к тому, что цветовые коды будут записываться в файл журнала, что сделает его менее читаемым.

  • 2
    На большинстве систем буферизуется тройник, поэтому выходные данные могут появиться только после завершения сценария. Кроме того, поскольку этот тройник работает в подоболочке, а не в дочернем процессе, ожидание нельзя использовать для синхронизации вывода с вызывающим процессом. Вам нужна небуферизованная версия тройника, похожая на bogomips.org/rainbows.git/commit/…
  • 0
    Это также может привести к утечке.
Показать ещё 27 комментариев
164

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

  • 0
    По некоторым причинам, в моем случае, когда скрипт выполняется из системного вызова c-program, два подпроцесса tee продолжают существовать даже после выхода из основного скрипта. Поэтому мне пришлось добавить ловушки, например так: exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
  • 12
    Я предлагаю прохождение -i в tee . В противном случае прерывания сигнала (ловушки) будут нарушать стандартный вывод в сценарии. Например, если вы trap 'echo foo' EXIT а затем нажимаете ctrl+c , вы не увидите « foo ». Поэтому я бы exec > >(tee -ia foo.log) ответ на exec > >(tee -ia foo.log) .
Показать ещё 3 комментария
25

Решение для 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
  • 0
    Это единственное решение, которое я видел до сих пор, которое работает на Mac
18

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

(
echo start
ls -l
echo end
) | tee foo.log
  • 5
    педантично, может также использовать фигурные скобки ( {} )
  • 0
    да, я подумал об этом, но это не перенаправление текущего stdout оболочки, это своего рода обман, вы фактически запускаете subshell и делаете для него обычное перенаправление piper. работает мысль. Я разделен с этим и решением "tail -f foo.log &". подожду немного, чтобы увидеть, может ли быть лучше поверхности. если не поскорее собираешься;)
Показать ещё 2 комментария
12

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

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

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

В качестве альтернативы отправьте журнал в отдельный файл:

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

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

9

Используя принятый ответ, мой 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.

  • 0
    Подобный ответ как вторая идея от Дэвида З. Посмотрите на его комментарии. +1 ;-)
  • 0
    Работает хорошо. Я не понимая $logfile часть tee < ${logfile}.pipe $logfile & . В частности, я попытался изменить это, чтобы перехватить полностью развернутые строки журнала команд (из set -x ) в файл, показывая только строки без начального «+» в stdout, изменив на (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile & но получил сообщение об ошибке, касающееся $logfile . Можете ли вы объяснить tee чуть более подробно?
1

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

0

Не могу сказать, что мне нравится любое решение на основе 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

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

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

  • 1
    Хвост оставит запущенный процесс во втором скрипте, который будет заблокирован, или вам нужно будет запустить его с &, и в этом случае он выйдет из процесса, как в первом.
  • 0
    @Vitaly: упс, забыл фон tee - Я редактировал. Как я уже сказал, ни одно из них не является идеальным решением, но фоновые процессы будут убиты, когда их родительская оболочка завершится, так что вам не придется беспокоиться о том, что они будут затягивать ресурсы вечно.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню