如何将时间戳添加到STDERR重定向

时间:2009-10-02 03:54:40

标签: shell scripting

在bash / ksh中我们可以为STDERR重定向添加时间戳吗?

E.g。 myscript.sh 2> error.log中

我也想在日志上写一个时间戳。

13 个答案:

答案 0 :(得分:27)

如果您正在谈论每一行的最新时间戳,那么您可能希望在实际脚本中执行此操作(但如果您无权更改它,请参阅下面的一个非常好的解决方案) 。如果您只想在脚本开始编写之前在自己的行上标记日期,我会使用:

( 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只是一个测试程序,可以写入具有特定时间间隔的stdoutstderr

当您运行该命令时,实际上会将"d to stdout"写入stderr(但这是您的TTY设备或启动时可能已经存在的任何其他stdout)。带有时间戳的stderr行将写入您想要的文件。

答案 1 :(得分:25)

Debian / Ubuntu中的devscripts包中包含一个名为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

答案 2 :(得分:15)

这是一个使用{strong> pax 之类的while read循环的版本,但不需要额外的文件描述符或单独的脚本(尽管您可以使用一个)。它使用流程替换:

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

使用 pax's predate.sh

myscript.sh 2> >( predate.sh > error.log )

答案 3 :(得分:12)

来自moreutils包的程序ts将标准输入管道标准输出到标准输出,并在每行前面加上时间戳。

为stdout行添加前缀:command | ts
为stdout和stderr添加前缀:command 2>&1 | ts

答案 4 :(得分:4)

我喜欢那些可移植的shell脚本,但有点不安的是他们为每一行fork / exec date(1)。这是一个快速的perl单行程,可以更有效地执行相同操作:

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

要使用它,只需通过其stdin提供此命令输入:

(echo hi; sleep 1; echo lo) | perl -p -MPOSIX -e 'BEGIN {$|=1} $_ = strftime("%T ", localtime) . $_'

答案 5 :(得分:3)

我没有编写脚本来管道,而是更喜欢将记录器作为一个函数编写在脚本中,然后用括号将整个过程发送到它中,如下所示:

 # 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

答案 6 :(得分:2)

如果你想重定向回stdout我发现你必须这样做:

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

我不确定为什么我需要>前面的(,所以<(,但这就是有用的。

答案 7 :(得分:2)

我对所有当前的解决方案都太懒了......所以我想出了新的解决方案(适用于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将时间戳格式化为最小格式。

如果stdoutstderr没有引号(“或”),它应该像魅力一样工作。如果它有(或可能有)它最好使用:

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

答案 8 :(得分:0)

#!/bin/bash

DEBUG=1

LOG=$HOME/script_${0##*/}_$(date +%Y.%m.%d-%H.%M.%S-%N).log
ERROR=$HOME/error.log

exec 2> $ERROR
exec 1> >(tee -ai $LOG)

if [ $DEBUG = 0 ] ; then
    exec 4> >(xargs -i echo -e "[ debug ] {}")
else
    exec 4> /dev/null
fi

# test
echo " debug sth " >&4
echo " log sth normal "
type -f this_is_error
echo " errot sth ..." >&2
echo " finish ..." >&2>&4
# close descriptor 4
exec 4>&-

答案 9 :(得分:0)

这件事:nohup myscript.sh 2&gt; &gt;(读取行时;执行echo“$(date):$ {line}”; done&gt; mystd.err)&lt; / dev / null&amp;

这样工作但是当我注销并重新登录到服务器时,它不起作用。即使我的进程(此处为myscript.sh)仍然运行,这是mystd.err停止使用stderr流填充。

有人知道如何在mystd.err文件中找回丢失的stderr吗?

答案 10 :(得分:0)

以为我会增加2美分的价值..

#!/bin/sh

timestamp(){
  name=$(printf "$1%*s" `expr 15 - ${#1}`)
  awk "{ print strftime(\"%b %d %H:%M:%S\"), \"- $name -\", $$, \"- INFO -\", \$0; fflush() }";
}

echo "hi" | timestamp "process name" >> /tmp/proccess.log

printf“$ 1%* s”`expr 15 - $ {#1}`
将名称隔开以使其看起来不错,其中15是发出的最大空间,如果需要则增加

输出&gt;&gt;日期 - 进程名称 - 进程ID - 信息 - 消息

Jun 27 13:57:20 - process name     - 18866 - INFO - hi

答案 11 :(得分:0)

如何为剩余输出加时间戳,将所有内容重定向到stdout?

这个答案结合了上面的一些技巧,以及unix stackexchange herehere。假设bash&gt; = 4.2,但其他高级shell可能有效。对于&lt; 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}"
}

答案 12 :(得分:0)

按顺序进行重定向。试试这个:

给出脚本-

$: 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