我希望在命令输出的每一行前面加上一个时间戳。例如:

foo
bar
baz


将成为

[2011-12-13 12:20:38] foo
[2011-12-13 12:21:32] bar
[2011-12-13 12:22:20] baz


...其中被加前缀的时间为时间打印行的位置。我该如何实现?

评论

是否有Unix实用程序可将时间戳记添加到stdin?

#1 楼

moreutils包括ts,它可以很好地完成此操作:

$ echo -e "foo\nbar\nbaz" | ts '[%Y-%m-%d %H:%M:%S]'
[2011-12-13 22:07:03] foo
[2011-12-13 22:07:03] bar
[2011-12-13 22:07:03] baz


您想知道该服务器何时恢复启动吗?只需运行command | ts '[%Y-%m-%d %H:%M:%S]',问题已解决:D。

评论


我怎么不知道这件事?!?!?!这惊人地补充了尾巴-f!尾-f /tmp/script.results.txt | ts

–布鲁诺·布鲁诺斯基(Bruno Bronosky)
2015年3月5日在22:51



如果我没有ts命令,应该怎么用?

– ekassis
17年1月16日在11:48

如果不起作用,请尝试将stderr重定向到stdout,例如ssh -v 127.0.0.1 2>&1 | ts

– jchook
17 Mar 20 '17在19:02



通过在Debian上执行sudo apt install moreutils进行安装,在Fedora上执行yum install moreutils进行安装。

–山姆·伯恩斯坦
18年6月19日在18:21

我认为指出参数-s很有用。这样将显示命令的运行时。我个人喜欢同时使用ts和ts -s。看起来像这样: ts -s'(%H:%M:%。S)]'| ts'[%Y-%m-%d%H:%M:%S'。这样就增加了如下日志行:[2018-12-04 08:31:00(00:26:28.267126)] Hai <3

– BrainStone
18/12/4在9:03

#2 楼

首先,如果您期望这些时间戳实际上代表一个事件,请记住,由于许多程序执行行缓冲(某些程序比其他程序更具攻击性),因此考虑这一点很重要,因为它接近原始行的时间而不是打印动作的时间戳。

您可能还需要检查命令是否没有专门用于执行此操作的内置功能。例如,某些版本的ping -D中存在ping,并在每行之前打印自Unix时代以来的时间。但是,如果您的命令不包含自己的方法,则可以使用一些方法和工具,其中包括:

POSIX shell

请记住,因为许多shell在内部将它们的字符串存储为cstrings,如果输入包含null字符(q4312079q),则可能导致该行过早结束。
/>
command | while IFS= read -r line; do printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$line"; done


Perl

command | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S]"), 
command | perl -pe 'use POSIX strftime; print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'
}'


Python

command | python -c 'import sys,time;sys.stdout.write("".join(( " ".join((time.strftime("[%Y-%m-%d %H:%M:%S]", time.localtime()), line)) for line in sys.stdin )))'



Ruby

command | ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'


评论


这里的一个问题是,当许多程序的标准输出是管道而不是终端时,它们会打开更多的输出缓冲。

– cjm
2011-12-13 18:02

@cjm-是的。可以使用stdbuf -o 0减轻某些输出缓冲,但是如果程序正在手动处理其输出缓冲,则将无济于事(除非可以选择禁用/减小输出缓冲区的大小)。

–克里斯唐(Chris Down)
2011-12-13 22:46

对于python,您可以使用python -u禁用行缓冲

– ibizaman
16年11月21日在22:01

sys.stdin中x的@Bwmat编号...在行上进行迭代,而无需先将它们全部缓存到内存中。

–克里斯唐(Chris Down)
16/12/26在12:36

这样做,您将获得缓冲... for 1 in 1 1 1 1;睡一觉回声;完成| python -c'import sys,time; sys.stdout.write(“”。join((“” .join((time.strftime(“ [%Y-%m-%d%H:%M:%S] sys.stdin)))中的“,time.gmtime()),line))

– ChuckCottrill
18年1月4日,0:06

#3 楼

对于逐行增量测量,请尝试使用gnomon。

它是一个命令行实用工具,有点像moreutils的ts,可以在其他命令的标准输出前添加时间戳信息。对于需要长时间记录历史的长时间运行的进程很有用。
输液要gnomon的任何内容都会在每行前面加上时间戳,表明该行是缓冲区中的最后一行有多久了-也就是说,下一行显示需要多长时间。默认情况下,gnomon将显示每行之间经过的秒数,但这是可配置的。



评论


使用实时进程时,它似乎是ts的绝佳替代品。 ts更适合非交互过程。

– BrainStone
18/12/4在9:05

#4 楼

我本来希望在上面发表评论,但我不能声名狼藉。无论如何,上面的Perl示例可以按以下方式进行缓冲:

command | perl -pe 'use POSIX strftime; 
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'


第一个'$ |'取消STDOUT的缓冲。第二个将stderr设置为当前的默认输出通道,并将其取消缓冲。由于select返回$ |的原始设置,因此通过将select包裹在select中,我们也可以重置$ |。默认为STDOUT。

是的,您可以按原样剪切'n粘贴。为了清晰起见,我对它进行了多行标记。

评论


就像魅力一样工作,而无需安装任何非标准软件包。

–杰伊·泰勒(Jay Taylor)
18-10-18在20:53

#5 楼

Ryan的帖子确实提供了一个有趣的想法,但是,它在几个方面都失败了。在使用tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') 进行测试时,我注意到即使stdout稍后出现,但相差几秒钟,时间戳也保持不变。考虑以下输出:

[2016-07-14 01:44:25] Jul 14 01:44:32 eagle dhclient[16091]: DHCPREQUEST of 192.168.0.78 on wlan7 to 255.255.255.255 port 67 (xid=0x411b8c21)
[2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: Joining mDNS multicast group on interface wlan7.IPv6 with address fe80::d253:49ff:fe3d:53fd.
[2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: New relevant interface wlan7.IPv6 for mDNS.


我提出的解决方案是类似的,但是提供了适当的时间戳记,并使用了更多的便携式printf而不是echo

| xargs -L 1 bash  -c 'printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" ' bash


为什么bash -c '...' bash?因为由于-c选项,第一个参数将分配给-c,并且不会出现在输出中。有关tail -f /var/log/syslog的正确说明,请参阅您的Shell的手册页。使用date测试此解决方案,以及(可能您猜到)断开并重新连接到我的wifi,已显示syslog均提供了正确的时间戳记和ksh消息

可以用任何类似bourne的shell替换bash,可以使用dash-c来完成,至少可以选择具有xargs选项的消息。

潜在问题:

该解决方案要求具有GNU findutils,它在POSIX兼容系统上可用,因此应该涵盖大多数类似Unix的系统。如果您的系统不兼容POSIX或没有q4312079q,显然无法使用

#6 楼

大多数答案都建议使用date,但是它足够慢。如果您的bash版本大于4.2.0,则最好使用printf,因为它是bash内置的。
如果您需要支持旧版bash版本,则可以根据bash版本创建log函数:

TIMESTAMP_FORMAT='%Y-%m-%dT%H:%M:%S'
# Bash version in numbers like 4003046, where 4 is major version, 003 is minor, 046 is subminor.
printf -v BV '%d%03d%03d' ${BASH_VERSINFO[0]} ${BASH_VERSINFO[1]} ${BASH_VERSINFO[2]}
if ((BV > 4002000)); then
log() {
    ## Fast (builtin) but sec is min sample for most implementations
    printf "%(${TIMESTAMP_FORMAT})T %5d %s\n" '-1' $$ "$*"  # %b convert escapes, %s print as is
}
else
log() {
    ## Slow (subshell, date) but support nanoseconds and legacy bash versions
    echo "$(date +"${TIMESTAMP_FORMAT}") $$ $*"
}
fi


查看速度差异:

user@host:~$time for i in {1..10000}; do printf "%(${TIMESTAMP_FORMAT})T %s\n" '-1' "Some text" >/dev/null; done

real    0m0.410s
user    0m0.272s
sys     0m0.096s
user@host:~$time for i in {1..10000}; do echo "$(date +"${TIMESTAMP_FORMAT}") Some text" >/dev/null; done

real    0m27.377s
user    0m1.404s
sys     0m5.432s


UPD:最好使用$(date +"${TIMESTAMP_FORMAT}")甚至$(exec date +"${TIMESTAMP_FORMAT}")来加速执行,而不是$(exec -c date +"${TIMESTAMP_FORMAT}")

评论


这个答案非常低估!显示相对性能影响特别好。

– Paul Hedderly
20-10-5在17:39



#7 楼

您可以使用datexargs来做到这一点:

... | xargs -L 1 echo `date +'[%Y-%m-%d %H:%M:%S]'` 


解释:

xargs -L 1告诉xargs每输入1行运行一次处理命令,并在第一行中传递。 echo `date +'[%Y-%m-%d %H:%M:%S]'` 基本上在输入参数的末尾回显日期

评论


该解决方案很接近,但是对于长时间分隔的输出而言,时间戳不正确。另外,您使用的是反引号,但没有引用$ 1。那不是好风格。始终引用变量。另外,您正在使用echo,它不是可移植的。没关系,但是在某些系统上可能无法正常工作。

– Sergiy Kolodyazhnyy
16年7月14日在7:56

经过测试之后,看来您是完全正确的...您是否知道有任何方法可以让每一行重新评估日期,还是几乎没有希望?

–瑞安
16年7月18日在1:14

#8 楼

我为解决这个确切问题而写的无耻插件:ets,用Go语言编写。

您可以在项目页面上找到很多用法示例。
与现有的显着区别答案和类似的产品是,ets旨在在pty(伪tty)中为您运行命令-也就是说,模拟在tty中本地运行的命令。与管道命令输出相比,例如ts,这使时间戳记几乎是透明的,并解决了一堆管道问题:

某些程序在写入管道时会主动缓冲,因此您看不到任何输出,然后看到一大堆输出(是的,您可以stdbuf,甚至可以将stdbuf和ts包装在别名/函数中,但是如果事情开箱即用就更好了);
某些程序在写入A时会禁用颜色和/或交互性管道;
除非打开管道失败,否则退出状态将消失;等。

命令可以直接执行,这意味着您可以简单地将ets放在现有的命令行之前,或者它们可以是shell命令(如上面的gif所示)。当然,如果您想通过管道输入,ets也可以这样做。它使用更合理的默认值(例如,单调时钟始终用于经过/递增的时间戳记),并且对自定义时区具有附加支持。这里有详细的比较。
再次,https://github.com/zmwangx/ets。试用一下,报告错误等。

#9 楼

将其放在脚本开头附近
# prepend a timestamp to all output
exec &> >( ts '%Y-%m-%d.%H%M.%.S ' )

,或者,为了获得额外的信誉,可以通过以下方式输出到日志文件:
script_name=foobar
log_file=$( printf "/tmp/${script_name}-%(%Y-%m-%d)T.%(%H%M%S)T.log" -1 )
echo "note: redirecting output to [${log_file}]"
exec &> >( ts '%Y-%m-%d %H:%M:%.S ' > ${log_file} )

输出到控制台和日志文件:
script_name=foobar
log_file=$( printf "/tmp/${script_name}-%(%Y-%m-%d)T.%(%H%M%S)T.log" -1 )
exec &> >( ts '%Y-%m-%d %H:%M:%.S ' | tee ${log_file} )

执行此操作的主要优点是将日志记录与其他所有记录分开,而不是通过在每个命令上通过管道传递到tee或类似内容来使脚本主体混乱,并且不必编写自定义日志记录功能并坚持使用旧版echo
ts软件包中找到了moreutils程序,该程序应在任何合理的健全管理制度下都可以轻松获得。 :-)