我希望在命令输出的每一行前添加一个时间戳。例如:
foo
bar
baz
会成为
[2011-12-13 12:20:38] foo
[2011-12-13 12:21:32] bar
[2011-12-13 12:22:20] baz
...这里的前缀时间是打印行的时间。我该如何实现?
我希望在命令输出的每一行前添加一个时间戳。例如:
foo
bar
baz
会成为
[2011-12-13 12:20:38] foo
[2011-12-13 12:21:32] bar
[2011-12-13 12:22:20] baz
...这里的前缀时间是打印行的时间。我该如何实现?
Answers:
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。
tail -f /tmp/script.results.txt | ts
ssh -v 127.0.0.1 2>&1 | ts
-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
首先,如果您期望这些时间戳实际上代表一个事件,请记住,由于许多程序执行行缓冲(某些程序比其他程序更具攻击性),因此考虑这一点很重要,因为它接近原始行的时间被打印,而不是动作的时间戳。
您可能还需要检查命令是否没有专用于此功能的内置功能。例如,它ping -D
存在于某些ping
版本中,并在每行之前打印自Unix纪元以来的时间。但是,如果您的命令不包含自己的方法,则可以使用一些方法和工具,其中包括:
请记住,由于许多shell在内部将它们的字符串存储为cstring,因此,如果输入包含空字符(\0
),则可能导致该行过早结束。
command | while IFS= read -r line; do printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$line"; done
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] ")'
stdbuf -o 0
,但是如果程序正在手动处理其输出缓冲,则将无济于事(除非有一个选项可以禁用/减小输出缓冲区的大小)。
python -u
... for x in sys.stdin
遍历行而不先将它们全部缓冲到内存中。
对于逐行增量测量,请尝试gnomon。
这是一个命令行实用程序,有点像moreutils的ts,用于将时间戳信息附加到另一个命令的标准输出中。对于长时间运行的过程很有用,在该过程中您需要花费很长时间的历史记录。
用管道输送要忽略的内容将在每行前面添加一个时间戳,指示该行是缓冲区中最后一行的时间-即下一行显示需要多长时间。默认情况下,gnomon将显示每行之间经过的秒数,但这是可配置的。
ts
使用实时流程的绝佳选择。虽然ts
更适合非交互式过程。
瑞安(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 一起测试此解决方案,已显示date
和syslog
消息均提供了正确的时间戳记
Bash可以用任何类似bourne的shell代替,可以使用ksh
或来完成dash
,至少可以-c
选择那些具有bash的选项。
该解决方案需要具有xargs
,它在POSIX兼容系统上可用,因此应涵盖大多数类似Unix的系统。如果您的系统不兼容POSIX或没有POSIX,显然无法正常工作GNU findutils
我本来希望在上面发表评论,但名声不能。无论如何,上面的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)'
多数答案都建议使用date
,但是速度很慢。如果您的bash版本大于4.2.0,则最好使用printf
bash内置。如果您需要支持旧版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}")
加速执行。
您可以使用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
基本上在输入的末尾回显日期
$1
。那不是好风格。始终引用变量。另外,您正在使用echo
,它不是可移植的。没关系,但是在某些系统上可能无法正常工作。
date
每一行得到重新评估,还是几乎没有希望?