Как заставить STDOUT и STDERR перейти на терминал и файл журнала?
У меня есть script, который будет запускаться интерактивно нетехническими пользователями. script записывает обновления состояния в STDOUT, чтобы пользователь мог убедиться, что script работает нормально.
Я хочу, чтобы оба STDOUT и STDERR перенаправлялись на терминал (чтобы пользователь мог видеть, что работает script, и посмотреть, была ли проблема). Я также хочу, чтобы оба потока были перенаправлены на файл журнала.
Я видел множество решений в сети. Некоторые не работают, а другие ужасно сложны. Я разработал работоспособное решение (которое я введу в качестве ответа), но это kludgy.
Идеальное решение - это одна строка кода, которая может быть включена в начало любого script, который отправляет оба потока как на терминал, так и на файл журнала.
РЕДАКТИРОВАТЬ: Перенаправление STDERR в STDOUT и соединение результата с типом работы, но это зависит от того, как пользователи запоминают перенаправление и вывод на выход. Я хочу, чтобы журнал был безупречным и автоматическим (вот почему я хотел бы встроить решение в сам script.)
Ответы
Ответ 1
Используйте "tee" для перенаправления на файл и на экран. В зависимости от используемой оболочки вы сначала должны перенаправить stderr на stdout, используя
./a.out 2>&1 | tee output
или
./a.out |& tee output
В csh имеется встроенная команда под названием "script", которая будет захватывать все, что идет на экран, в файл. Вы запустите его, набрав "script", затем сделав то, что вы хотите захватить, затем нажмите control-D, чтобы закрыть файл script. Я не знаю эквивалента для sh/bash/ksh.
Кроме того, поскольку вы теперь указали, что это ваши собственные скрипты sh, которые вы можете изменить, вы можете сделать перенаправление внутренне, окружив весь script скобками или скобками, например
#!/bin/sh
{
... whatever you had in your script before
} 2>&1 | tee output.file
Ответ 2
Приближается пол-десятилетия спустя...
Я считаю, что это "идеальное решение", которое ищет ОП.
Здесь один вкладыш, который вы можете добавить в начало Bash script:
exec > >(tee -a $HOME/logfile) 2>&1
Здесь небольшой script, демонстрирующий его использование:
#!/usr/bin/env bash
exec > >(tee -a $HOME/logfile) 2>&1
# Test redirection of STDOUT
echo test_stdout
# Test redirection of STDERR
ls test_stderr___this_file_does_not_exist
(Примечание: это работает только с Bash. Работает с с /bin/sh.)
Адаптировано из здесь; оригинал не сделал, из того, что я могу сказать, поймать STDERR в лог файле. Исправлено примечание от здесь.
Ответ 3
для перенаправления stderr на stdout добавьте это в свою команду: 2>&1
Для вывода на терминал и входа в файл вы должны использовать tee
Оба вместе выглядят следующим образом:
mycommand 2>&1 | tee mylogfile.log
EDIT: для внедрения в ваш script вы сделаете то же самое. Итак, ваш script
#!/bin/sh
whatever1
whatever2
...
whatever3
закончится как
#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log
Ответ 4
Используйте программу tee и dup stderr для стандартного вывода.
program 2>&1 | tee > logfile
Ответ 5
Я создал script под названием "RunScript.sh". Содержимое этого script:
${APP_HOME}/${1}.sh ${2} ${3} ${4} ${5} ${6} 2>&1 | tee -a ${APP_HOME}/${1}.log
Я называю это следующим образом:
./RunScript.sh ScriptToRun Param1 Param2 Param3 ...
Это работает, но требует, чтобы скрипты приложений выполнялись через внешний script. Это немного клочья.
Ответ 6
Через год, здесь старый bash script для записи чего-либо. Например, фотографии
teelog make ...
записывается в сгенерированное имя журнала (и см. трюк для входа в вложенный make
).
#!/bin/bash
me=teelog
Version="2008-10-9 oct denis-bz"
Help() {
cat <<!
$me anycommand args ...
logs the output of "anycommand ..." as well as displaying it on the screen,
by running
anycommand args ... 2>&1 | tee `day`-command-args.log
That is, stdout and stderr go to both the screen, and to a log file.
(The Unix "tee" command is named after "T" pipe fittings, 1 in -> 2 out;
see http://en.wikipedia.org/wiki/Tee_(command) ).
The default log file name is made up from "command" and all the "args":
$me cmd -opt dir/file logs to `day`-cmd--opt-file.log .
To log to xx.log instead, either export log=xx.log or
$me log=xx.log cmd ...
If "logdir" is set, logs are put in that directory, which must exist.
An old xx.log is moved to /tmp/\$USER-xx.log .
The log file has a header like
# from: command args ...
# run: date pwd etc.
to show what was run; see "From" in this file.
Called as "Log" (ln -s $me Log), Log anycommand ... logs to a file:
command args ... > `day`-command-args.log
and tees stderr to both the log file and the terminal -- bash only.
Some commands that prompt for input from the console, such as a password,
don't prompt if they "| tee"; you can only type ahead, carefully.
To log all "make" s, including nested ones like
cd dir1; \$(MAKE)
cd dir2; \$(MAKE)
...
export MAKE="$me make"
!
# See also: output logging in screen(1).
exit 1
}
#-------------------------------------------------------------------------------
# bzutil.sh denisbz may2008 --
day() { # 30mar, 3mar
/bin/date +%e%h | tr '[A-Z]' '[a-z]' | tr -d ' '
}
edate() { # 19 May 2008 15:56
echo `/bin/date "+%e %h %Y %H:%M"`
}
From() { # header # from: $* # run: date pwd ...
case `uname` in Darwin )
mac=" mac `sw_vers -productVersion`"
esac
cut -c -200 <<!
${comment-#} from: [email protected]
${comment-#} run: `edate` in $PWD `uname -n` $mac `arch`
!
# mac $PWD is pwd -L not -P real
}
# log name: day-args*.log, change this if you like --
logfilename() {
log=`day`
[[ $1 == "sudo" ]] && shift
for arg
do
log="$log-${arg##*/}" # basename
(( ${#log} >= 100 )) && break # max len 100
done
# no blanks etc in logfilename please, tr them to "-"
echo $logdir/` echo "$log".log | tr -C '.:+=[:alnum:]_\n' - `
}
#-------------------------------------------------------------------------------
case "$1" in
-v* | --v* )
echo "$0 version: $Version"
exit 1 ;;
"" | -* )
Help
esac
# scan log= etc --
while [[ $1 == [a-zA-Z_]*=* ]]; do
export "$1"
shift
done
: ${logdir=.}
[[ -w $logdir ]] || {
echo >&2 "error: $me: can't write in logdir $logdir"
exit 1
}
: ${log=` logfilename "[email protected]" `}
[[ -f $log ]] &&
/bin/mv "$log" "/tmp/$USER-${log##*/}"
case ${0##*/} in # basename
log | Log ) # both to log, stderr to caller stderr too --
{
From "[email protected]"
"[email protected]"
} > $log 2> >(tee /dev/stderr) # bash only
# see http://wooledge.org:8000/BashFAQ 47, stderr to a pipe
;;
* )
#-------------------------------------------------------------------------------
{
From "[email protected]" # header: from ... date pwd etc.
"[email protected]" 2>&1 # run the cmd with stderr and stdout both to the log
} | tee $log
# mac tee buffers stdout ?
esac
Ответ 7
Это помогает, а также сохраняет различие между stdout и stderr:
{ the_cmd > >(tee stdout.txt ); } 2> >(tee stderr.txt >&2 )
Вот скрипт:
the_cmd()
{
echo out;
1>&2 echo err;
}
{ the_cmd > >(tee stdout.txt ); } 2> >(tee stderr.txt >&2 )
Здесь сессия:
$ foo=$(./example.sh)
err
$ echo $foo
out
$ cat stdout.txt
out
$ cat stderr.txt
err
Вот как это работает:
-
the_cmd
отправляется в tee
, который сохраняет его в файл, но также передает копию в стандартный вывод своего блока (часть в {фигурных скобках}). stderr из the_cmd
по the_cmd
делает его блоком stderr.
-
Мы не перенаправляем стандартный вывод из блока (так как мы уже записали его в файл), поэтому он по умолчанию переносит его в терминал, но мы перенаправляем блок stderr на другую команду tee (поскольку нам все еще нужно захватить его).
-
Последняя команда tee
записывает свой stdin (который является the_cmd
) в файл, но также передает копию в stdout, которая смешивает его с stdout блока (мы этого не хотим), поэтому мы явно отправляем его обратно в stderr с >&2
.
Это удерживает stderr отдельно от stdout как в файлах, так и в выходных данных команды.
Если первая тройка записывает какие-либо ошибки, они будут отображаться как в файле stderr, так и в команде stderr, если секундная тройка записывает какие-либо ошибки, они будут отображаться только в терминале stderr.
Ответ 8
Используйте команду script
в script (man 1 script)
Создайте оболочку shellscript (2 строки), которая устанавливает script(), а затем вызывает exit.
Часть 1: wrap.sh
#!/bin/sh
script -c './realscript.sh'
exit
Часть 2: realscript.sh
#!/bin/sh
echo 'Output'
Результат:
~: sh wrap.sh
Script started, file is typescript
Output
Script done, file is typescript
~: cat typescript
Script started on fr. 12. des. 2008 kl. 18.07 +0100
Output
Script done on fr. 12. des. 2008 kl. 18.07 +0100
~: