Как добавить метку времени в перенаправление STDERR - PullRequest
33 голосов
/ 02 октября 2009

В bash / ksh мы можем добавить метку времени к перенаправлению STDERR?

например. myscript.sh 2> error.log

Я хочу получить метку времени, записанную в журнале.

Ответы [ 13 ]

27 голосов
/ 02 октября 2009

Если вы говорите о современной отметке времени в каждой строке, это то, что вы, вероятно, захотите сделать в своем реальном сценарии (но см. Ниже для изящного решения, если у вас нет полномочий изменить его) , Если вы хотите, чтобы маркер даты находился в отдельной строке до того, как ваш скрипт начал писать, я бы использовал:

( date 1>&2 ; myscript.sh ) 2>error.log

Вам нужен трюк для передачи stderr через другую программу, которая может добавлять временные метки к каждой строке. Вы можете сделать это с помощью программы на C, но есть гораздо более хитрый способ, используя просто bash.

Сначала создайте скрипт, который будет добавлять метку времени к каждой строке (она называется predate.sh):

#!/bin/bash
while read line ; do
    echo "$(date): ${line}"
done

Например:

( echo a ; sleep 5 ; echo b ; sleep 2 ; echo c ) | ./predate.sh

производит:

Fri Oct  2 12:31:39 WAST 2009: a
Fri Oct  2 12:31:44 WAST 2009: b
Fri Oct  2 12:31:46 WAST 2009: c

Тогда вам понадобится еще один трюк, который может поменять stdout и stderr, это маленькое чудовище здесь:

( myscript.sh 3>&1 1>&2- 2>&3- )

Тогда просто объединить два трюка, отметив время stdout и перенаправив его в ваш файл:

( myscript.sh 3>&1 1>&2- 2>&3- ) | ./predate.sh >error.log

Следующая транскрипция показывает это в действии:

pax> cat predate.sh
    #!/bin/bash
    while read line ; do
        echo "$(date): ${line}"
    done
pax> cat tstdate.sh
    #!/bin/bash
    echo a to stderr then wait five seconds 1>&2
    sleep 5
    echo b to stderr then wait two seconds 1>&2
    sleep 2
    echo c to stderr 1>&2
    echo d to stdout
pax> ( ( ./tstdate.sh ) 3>&1 1>&2- 2>&3- ) | ./predate.sh >error.log
    d to stdout
pax> cat error.log
    Fri Oct  2 12:49:40 WAST 2009: a to stderr then wait five seconds
    Fri Oct  2 12:49:45 WAST 2009: b to stderr then wait two seconds
    Fri Oct  2 12:49:47 WAST 2009: c to stderr

Как уже упоминалось, predate.sh будет ставить перед каждой строкой метку времени, а tstdate.sh - это просто тестовая программа для записи в stdout и stderr с определенными временными промежутками.

Когда вы запускаете команду, вы фактически записываете "d to stdout" в stderr (но это ваше устройство TTY или что-то еще, что stdout могло быть при запуске). Отметка времени stderr строк записывается в нужный файл.

25 голосов
/ 02 октября 2009

Пакет devscripts в Debian / Ubuntu содержит скрипт с именем annotate-output, который делает это (как для stdout, так и для stderr).

$ annotate-output make
21:41:21 I: Started make
21:41:21 O: gcc -Wall program.c
21:43:18 E: program.c: Couldn't compile, and took me ages to find out
21:43:19 E: collect2: ld returned 1 exit status
21:43:19 E: make: *** [all] Error 1
21:43:19 I: Finished with exitcode 2
16 голосов
/ 02 октября 2009

Вот версия, которая использует цикл while read, такой как pax's , но не требует дополнительных файловых дескрипторов или отдельного скрипта (хотя вы могли бы использовать один). Используется процесс подстановки:

myscript.sh 2> >( while read line; do echo "$(date): ${line}"; done > error.log )

Использование pax's predate.sh:

myscript.sh 2> >( predate.sh > error.log )
12 голосов
/ 12 января 2013

Программа ts из пакета moreutils направляет стандартный ввод к стандартному выводу и ставит перед каждой строкой метку времени.

Для префикса строк stdout: command | ts
Для префикса как stdout, так и stderr: command 2>&1 | ts

4 голосов
/ 02 марта 2012

Мне нравятся эти переносимые сценарии оболочки, но немного беспокоит то, что они форка / exec date (1) для каждой строки. Вот быстрый Perl One-Liner, чтобы сделать то же самое более эффективно:

perl -p -MPOSIX -e 'BEGIN {$!=1} $_ = strftime("%T ", localtime) . $_'

Чтобы использовать его, просто введите эту команду через стандартный ввод:

(echo hi; sleep 1; echo lo) | perl -p -MPOSIX -e 'BEGIN {$|=1} $_ = strftime("%T ", localtime) . $_'
3 голосов
/ 22 октября 2012

Вместо того, чтобы писать скрипт для передачи, я предпочитаю записывать регистратор как функцию внутри скрипта, а затем отправлять весь процесс в него в скобках, например:

 # Vars    
 logfile=/path/to/scriptoutput.log

 # Defined functions
 teelogger(){
   log=$1
   while read line ; do
     print "$(date +"%x %T") :: $line" | tee -a $log
   done
 }


# Start process
{

echo 'well'
sleep 3
echo 'hi'
sleep 3
echo 'there'
sleep 3
echo 'sailor'

}  |  teelogger $logfile
2 голосов
/ 03 ноября 2016

Я был слишком ленив для всех существующих решений ... Так что я нашел новое (работы для stdout также можно настроить для stderr):

echo "output" | xargs -L1 -I{} bash -c "echo \$(date +'%x %T') '{}'" | tee error.log

сохранит в файл и напечатает что-то вроде этого: 11/3/16 16:07:52 output

подробности:

-L1 означает «для каждой новой строки»

-I{} означает «заменить {} вводом»

bash -c используется для обновления $(date) каждый раз для нового вызова

%x %T форматирует временную метку в минимальной форме.

Он должен работать как шарм, если stdout и stderr не имеют кавычек ("или`). Если он имеет (или может иметь), лучше использовать:

echo "output" | awk '{cmd="(date +'%H:%M:%S')"; cmd | getline d; print d,$0; close(cmd)} | tee error.log'

(взято из моего ответа в другой теме: https://stackoverflow.com/a/41138870/868947)

2 голосов
/ 15 июля 2010

Если вы хотите перенаправить обратно на стандартный вывод, я обнаружил, что вы должны сделать это:

myscript.sh >> >( while read line; do echo "$(date): ${line}"; done )

Не уверен, зачем мне > перед (, поэтому <(, но это то, что работает.

0 голосов
/ 25 сентября 2018

Перенаправления принимаются по порядку. Попробуйте это:

С учетом сценария -

$: cat tst
echo a
sleep 2
echo 1 >&2
echo b
sleep 2
echo 2 >&2
echo c
sleep 2
echo 3 >&2
echo d

Я получаю следующее

$: ./tst 2>&1 1>stdout | sed 's/^/echo $(date +%Y%m%dT%H%M%S) /; e'
20180925T084008 1
20180925T084010 2
20180925T084012 3

И как бы мне не нравился awk, он на сегодняшний день избегает избыточных субколлов.

$: ./tst 2>&1 1>stdout | awk "{ print strftime(\"%Y%m%dT%H%M%S \") \$0; fflush() }"; >stderr

$: cat stderr
20180925T084414 1
20180925T084416 2
20180925T084418 3

$: cat stdout
a
b
c
d
0 голосов
/ 31 января 2017

Как насчет временной отметки оставшегося вывода, перенаправляя все на стандартный вывод?

Этот ответ объединяет некоторые методы, описанные выше, а также из unix stack exchange здесь и здесь . bash> = 4.2, но другие расширенные оболочки могут работать. Для <<code>4.2 замените printf на (более медленный) вызов date.

: ${TIMESTAMP_FORMAT:="%F %T"} # override via environment
_loglines() { 
    while IFS= read -r _line ; do 
      printf "%(${TIMESTAMP_FORMAT})T#%s\n" '-1' "$_line";
    done;
}
exec 7<&2 6<&1
exec &> >( _loglines )
# Logit

Для восстановления stdout / stderr:

exec 1>&6 2>&7

Затем вы можете использовать tee для отправки меток времени в stdout и в файл журнала.

_tmpfile=$(mktemp)
exec &> >( _loglines | tee $_tmpfile )

Неплохая идея иметь код очистки, если процесс завершился без ошибок:

trap "_cleanup \$?" 0 SIGHUP SIGINT SIGABRT SIGBUS SIGQUIT SIGTRAP SIGUSR1 SIGUSR2 SIGTERM
_cleanup() { 
    exec >&6 2>&7
    [[ "$1" != 0 ]] && cat "$_logtemp"
    rm -f "$_logtemp"
    exit "${1:-0}"
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...