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

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

exec > foo.log
echo test

это поместит «тест» в foo. журнальный файл.

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

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

script | tee foo.log

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

Я пытался

exec | tee foo.log

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

вопрос задан 3.07.2010
Vitaly Kushner
6276 репутация

9 ответов


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

    ответ дан DevSolar, с репутацией 46454, 4.08.2010
  • 151 рейтинг

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

    ответ дан Adam Spiers, с репутацией 12038, 9.08.2012
  • 23 рейтинг

    Решение для занятых и небашных оболочек

    Принятый ответ, безусловно, лучший выбор для 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 $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 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
    
    ответ дан jbarlow, с репутацией 1187, 5.03.2011
  • 18 рейтинг

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

    (
    echo start
    ls -l
    echo end
    ) | tee foo.log
    
    ответ дан WReach, с репутацией 16440, 3.07.2010
  • 11 рейтинг

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

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

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

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

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

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

    ответ дан Tobu, с репутацией 19769, 7.12.2014
  • 9 рейтинг

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

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

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

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

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

    ответ дан fgunger, с репутацией 91, 9.07.2011
  • 1 рейтинг

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

    ответ дан Dennis Williamson, с репутацией 228759, 4.07.2010
  • 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
    

    Вы можете настроить это, e. г. вместо этого сделайте tee = false по умолчанию, вместо этого установите TEE вместо файла журнала и т. д. Я думаю, что это решение похоже на jbarlow, но проще, может быть, у меня есть ограничения, с которыми я еще не сталкивался.

    ответ дан Oliver, с репутацией 14880, 26.07.2018
  • -1 рейтинг

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

    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
    

    Второй оставил бы трубу без дела, если что-то пойдет не так с вашим скриптом, что может быть или не быть проблемой (i. е. может быть, вы могли бы rm в родительской оболочке впоследствии).

    ответ дан David Z, с репутацией 91674, 3.07.2010