在命令的每行输出前添加时间戳


Answers:


274

moreutils includes ts可以很好地做到这一点:

command | ts '[%Y-%m-%d %H:%M:%S]'

它也消除了循环的需要,每行输出都将带有时间戳。

$ 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

您想知道该服务器何时恢复启动吗?只需运行ping | ts,问题就解决了:D。


8
我怎么不知道这件事?!?!?!这完美地补充了尾巴-f!tail -f /tmp/script.results.txt | ts
布鲁诺·布鲁诺斯基

cygwin呢?有类似的东西吗?似乎没有Joey的moreutils在那。
CrazyPenguin

3
如果我没有ts命令,应该怎么用?
ekassis '17

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

3
我认为指出该参数-s很有用。这样将显示命令的运行时。我个人喜欢同时使用ts,并ts -s在同一时间。看起来是这样的:command | 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

100

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

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

POSIX外壳

请记住,由于许多shell在内部将它们的字符串存储为cstring,因此,如果输入包含空字符(\0),则可能导致该行过早结束。

command | while IFS= read -r line; do printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$line"; done

GNU AWK

command | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }'

佩尔

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

蟒蛇

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 )))'

红宝石

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

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

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

2
对于python,您可以使用python -u
ibizaman

@Bwmat No. ... for x in sys.stdin遍历行而不先将它们全部缓冲到内存中。
克里斯·

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

41

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

这是一个命令行实用程序,有点像moreutils的ts,用于将时间戳信息附加到另一个命令的标准输出中。对于长时间运行的过程很有用,在该过程中您需要花费很长时间的历史记录。

用管道输送要忽略的内容将在每行前面添加一个时间戳,指示该行是缓冲区中最后一行的时间-即下一行显示需要多长时间。默认情况下,gnomon将显示每行之间经过的秒数,但这是可配置的。

gnomon演示


看起来是ts使用实时流程的绝佳选择。虽然ts更适合非交互式过程。
BrainStone

7

瑞安(Ryan)的帖子确实提供了一个有趣的想法,但是它在几个方面都失败了。在使用进行测试时tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1 ,我注意到时间戳记保持不变,即使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选项的原因,第一个参数将分配给$0输出,并且不会出现在输出中。请查阅您的Shell手册页以获取有关以下内容的正确说明:-c

通过tail -f /var/log/syslog与(和您可能猜到的)断开连接并重新连接到我的wifi 一起测试此解决方案,已显示datesyslog消息均提供了正确的时间戳记

Bash可以用任何类似bourne的shell代替,可以使用ksh或来完成dash,至少可以-c选择那些具有bash的选项。

潜在问题:

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


5

我本来希望在上面发表评论,但名声不能。无论如何,上面的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粘贴。为了清晰起见,我对其进行了多行标记。

如果您真的想变得精确(并且您已经安装了Time :: Hires):

command | perl -pe 'use POSIX strftime; use Time::HiRes gettimeofday;
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    ($s,$ms)=gettimeofday();
                    $ms=substr(q(000000) . $ms,-6);
                    print strftime "[%Y-%m-%d %H:%M:%S.$ms]", localtime($s)'

1
就像魅力一样,无需安装任何非标准软件包。
杰伊·泰勒

2

多数答案都建议使用date,但是速度很慢。如果您的bash版本大于4.2.0,则最好使用printfbash内置。如果您需要支持旧版bash版本,则可以log根据bash版本创建功能:

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}")加速执行。


0

您可以使用date和完成此操作xargs

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

说明:

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


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

经过测试之后,看来您是完全正确的...您是否知道有任何方法可以使date每一行得到重新评估,还是几乎没有希望?
瑞安
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.