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