Как мне получить STDOUT и STDERR, чтобы перейти к терминалу и файлу журнала?


У меня есть скрипт, который будет выполняться в интерактивном режиме неспециалистами. Сценарий записывает обновления состояния в STDOUT, чтобы пользователь мог быть уверен, что сценарий работает нормально.

Я хочу, чтобы STDOUT и STDERR перенаправлялись на терминал (чтобы пользователь мог видеть, что скрипт работает, а также видеть, есть ли проблема). Я также хочу, чтобы оба потока перенаправлялись в файл журнала.

Я видел кучу решений в сети. Некоторые из них не работают, а другие ужасно сложно. Я разработал работоспособное решение (которое я введу в качестве ответа), но это kludgy.

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

EDIT: перенаправление STDERR в STDOUT и передача результата в tee работает, но это зависит от пользователей, которые не забывают перенаправлять и передавать вывод. Я хочу, чтобы ведение журнала быть надежным и автоматическим (именно поэтому я хотел бы иметь возможность встроить решение в сам скрипт.)

7 66

7 ответов:

используйте "тройник" для перенаправления на файл и экран. В зависимости от оболочки, которую вы используете, вы должны перенаправить stderr в stdout, используя

./a.out 2>&1 | tee output

или

./a.out |& tee output

В csh есть встроенная команда под названием "script", которая будет захватывать все, что идет на экран в файл. Вы начинаете его, набрав "script", затем делаете все, что хотите захватить, а затем нажимаете control-D, чтобы закрыть файл сценария. Я не знаю эквивалента для ш/Баш/КШ.

кроме того, поскольку теперь вы указали, что это ваши собственные скрипты sh, которые вы можете изменить, вы можете сделать перенаправление внутри, окружив весь скрипт фигурными скобками или скобками, например

  #!/bin/sh
  {
    ... whatever you had in your script before
  } 2>&1 | tee output.file

приближается к половине десятилетия спустя...

Я считаю, что это" идеальное решение", которое ищет OP.

вот один лайнер, который вы можете добавить в верхнюю часть вашего скрипта Bash:

exec > >(tee -a $HOME/logfile) 2>&1

вот небольшой скрипт, демонстрирующий его использование:

#!/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 в лог-файле. Исправлено с примечанием от здесь.

чтобы перенаправить stderr в stdout добавьте это в вашей команде: 2>&1 Для вывода на терминал и входа в файл вы должны использовать tee

вместе будет выглядеть так:

 mycommand 2>&1 | tee mylogfile.log

EDIT: для встраивания в ваш скрипт вы бы сделали то же самое. Так что ваш скрипт

#!/bin/sh
whatever1
whatever2
...
whatever3

как

#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log

используйте программу tee и DUP stderr для stdout.

 program 2>&1 | tee > logfile

Я создал скрипт под названием "RunScript.sh". содержание этого скрипта:

${APP_HOME}/.sh      2>&1 | tee -a ${APP_HOME}/.log

Я называю это так:

./RunScript.sh ScriptToRun Param1 Param2 Param3 ...

это работает, но для этого требуется, чтобы скрипты приложения запускались через внешний скрипт. Это немного запутано.

год спустя, вот старый скрипт bash для регистрации чего-либо. Например,
teelog make ... регистрирует сгенерированное имя журнала (и см. трюк для регистрации вложенных makes тоже.)

#!/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: $@
${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`
    [[  == "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 "" in
-v* | --v* )
    echo " version: $Version"
    exit 1 ;;
"" | -* )
    Help
esac

    # scan log= etc --
while [[  == [a-zA-Z_]*=* ]]; do
    export ""
    shift
done

: ${logdir=.}
[[ -w $logdir ]] || {
    echo >&2 "error: $me: can't write in logdir $logdir"
    exit 1
    }
: ${log=` logfilename "$@" `}
[[ -f $log ]]  &&
    /bin/mv "$log" "/tmp/$USER-${log##*/}"


case ${0##*/} in  # basename
log | Log )  # both to log, stderr to caller's stderr too --
{
    From "$@"
    "$@"
} > $log  2> >(tee /dev/stderr)  # bash only
    # see http://wooledge.org:8000/BashFAQ 47, stderr to a pipe
;;

* )
#-------------------------------------------------------------------------------
{
    From "$@"  # header: from ... date pwd etc.

    "$@"  2>&1  # run the cmd with stderr and stdout both to the log

} | tee $log
    # mac tee buffers stdout ?

esac

использовать 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
~: