en
ru
1 / 1

Метка: bash

  1. 4 июня 2016 Как сохранить копию вывода скрипта в лог

    В некоторых ситуациях бывает нужно перенаправить вывод в stdout и stderr из шел-скрипта в лог, сохранив его при этом и в терминале тоже. Обычно в таких случаях я заворачивал интересующие команды в фигурные скобки и перенаправлял полученную составную команду на вход tee. В скрипте ниже более прямой способ добиться того же самого результата с помощью именованных пайпов.

    #!/usr/bin/env bash
    
    # stdout и stderr будут перенаправлены в этот файл.
    log_file="script.log"
    
    # Завершить скрипт, если какая-либо из команд вернёт ошибку, которая не
    # будет обработана.
    set -e
    
    # Для создания временного пайпа нет специальной команды, так что заводим
    # временную директорию, в которую помещаем пайп.
    fifo_dir="$(mktemp -d)"
    fifo="${fifo_dir}/fifo"
    mkfifo "${fifo}"
    
    # Команда `tee` дублирует все данные, прочитанные из пайпа, и в stdout,
    # и в лог-файл.
    tee "${log_file}" < "${fifo}" & tee_pid=$!
    
    # Перенаправляем stdout и stderr в пайп.
    exec &> "${fifo}"
    
    cleanup() {
        # Запоминаем результат последней команды.
        code=$?
    
        # Разрешаем функции завершиться, даже если в процессе возникнет ошибка.
        set +e
    
        echo "Exiting with code ${code}"
    
        # Нужно закрыть дескрипторы stdout и stderr, чтобы процесс `tee`
        # завершился.
        exec 1>&- 2>&-
        wait ${tee_pid}
        rm -rf "${fifo_dir}"
    
        # Здесь можно сделать что-нибудь с получившимся лог-файлом, например:
        # cat "${log_file}" | mail -s "${log_file}" user@example.com
    
        exit ${code}
    }
    
    # cleanup() вызовется с правильным кодом возврата даже если скрипт будет
    # остановлен преждевременно из-за ошибки (см. `set -e` выше).
    trap cleanup EXIT
    
    # Здесь должно быть основное тело скрипта.
    # Нужно отметить, что, если процесс скрипта будет в какой-то момент
    # замещён командой `exec`, функция cleanup() не вызовется, и временные файлы
    # не будут удалены.
    $@
    

    Закрытие выходных дескрипторов и ожидание завершения процесса tee нужно, только если хочется сбросить буферы лог-файла, если с ним будут производиться какие-то дополнительные действия внутри скрипта. Иначе достаточно удалить временную директорию.

    Есть и другой способ получить аналогичный эффект, используя встроенную в bash команду coproc, который описан, например, здесь и обсуждение на связанную с coproc тему здесь. У него есть определённые преимущества (например, не нужен доступ на запись в директорию для создания временных файлов), но лично я продолжу использовать именованные пайпы, т.к. код с ними выглядит понятнее.