Shell 如何向STDERR重定向添加时间戳

Shell 如何向STDERR重定向添加时间戳,shell,scripting,Shell,Scripting,在bash/ksh中,我们可以向STDERR重定向添加时间戳吗 例如,myscript.sh 2>error.log 我也想在日志上写一个时间戳。如果你说的是每一行上的最新时间戳,那可能是你想在实际脚本中做的事情(但如果你没有能力更改它,请参阅下面的漂亮解决方案)。如果您只想在脚本开始编写之前在自己的行上标记日期,我会使用: ( date 1>&2 ; myscript.sh ) 2>error.log 您需要的是一个技巧,通过另一个可以向每行添加时间戳的程序来传递stde

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

例如,myscript.sh 2>error.log


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

如果你说的是每一行上的最新时间戳,那可能是你想在实际脚本中做的事情(但如果你没有能力更改它,请参阅下面的漂亮解决方案)。如果您只想在脚本开始编写之前在自己的行上标记日期,我会使用:

( date 1>&2 ; myscript.sh ) 2>error.log
您需要的是一个技巧,通过另一个可以向每行添加时间戳的程序来传递stderr。您可以使用C程序来实现这一点,但是使用
bash
有一种更迂回的方法

首先,创建一个脚本,将时间戳添加到每一行(称为
predate.sh
):

例如:

( 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
行将写入所需的文件。

这是一个版本,它在读取时使用
循环,就像pax的,但不需要额外的文件描述符或单独的脚本(尽管可以使用)。它使用过程替换:

myscript.sh 2> >( while read line; do echo "$(date): ${line}"; done > error.log )
使用早于.sh的pax

#!/bin/bash
while read line ; do
    echo "$(date): ${line}"
done
myscript.sh 2> >( predate.sh > error.log )

Debian/Ubuntu中的
devscripts
包包含一个名为的脚本,它可以实现这一点(对于stdout和stderr)


如果要重定向回stdout,我发现您必须执行以下操作:

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

不知道为什么我需要
前面(
,所以
这个东西:nohup myscript.sh2>>(在读取行时,执行echo“$(日期):${line}”;done>mystd.err)
工作原理是这样的,但当我注销并重新登录到服务器时,它不工作。即mystd.err停止使用stderr流填充,即使我的进程(此处为myscript.sh)仍在运行


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

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

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) . $_'

我想我会加上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是最大发布空间,如果需要,请增加

输出>>日期-进程名称-进程ID-信息-消息

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

我宁愿将记录器作为脚本中的一个函数来编写,然后用括号将整个过程发送到脚本中,而不是将脚本写入管道,如下所示:

 # 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

包中的程序
ts
将标准输入传输到标准输出,并在每行前面加上时间戳

为标准输出行加前缀:
command|ts


要同时为stdout和stderr添加前缀:
command 2>&1|ts
对于所有当前的解决方案,我都太懒了……因此我找到了新的解决方案(适用于
stdout
也可以针对
stderr
进行调整):

将保存到文件并打印如下内容:
11/3/16 16:07:52输出

详情:

-L1
表示“每行新代码”

-I{}
表示“用输入替换{}”

bash-c
用于每次新呼叫时更新
$(日期)

%x%T
将时间戳格式化为最小格式

如果
stdout
stderr
没有引号(“或”)的话,它应该像一个符咒一样工作。如果它有(或可能有),最好使用:

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

(摘自我在另一个主题中的回答:)

给剩余的输出加上时间戳,将所有内容重定向到stdout怎么样

此答案结合了上面以及unix stackexchange和中的一些技术。
bash
=
4.2
,但其他高级shell可能会起作用。对于
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
然后可以使用
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}"
}

重定向按顺序进行。请尝试以下操作:

给剧本-

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

对于这两种情况,我都没有在error.log中得到任何信息(尽管我使用的是Cygwin)。这对你有用吗?我刚刚在Cygwin中试用过,但它不起作用,但在Ubuntu上效果很好。确实如此,只是在家里的Ubuntu中试用过。这一定是某种Cygwin问题。+1表示一个更简单、更简单但不太离谱的解决方案:-)谢谢你的代码。另外:我在stdout和stderr中使用它:
myscript.sh&>>(predate.sh>error.log)
好东西,简短,并且完美地完成了工作。如果你想把它放在一个文件中而不是stdout,你需要在“完成”和“')之间插入“>>文件名”,就像这样:myscript.sh>>(在读取line;do echo“$(日期):${line}”;done>>日志文件时)为什么需要交换输出流?@Cameron:因为管道
对标准输出进行操作,而OP希望对标准错误进行操作。但是
(日期1>&2;myscript.sh)2>error.log
将日期添加到
error.log
中,无论
是什么
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}"
}
$: 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
$: ./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