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