我希望每位文章通过一个简单小问题,来引导你们逐渐深入了解学习shell,潜移默化的把握shell编程的最佳实践。本文通过引入一个小问题以及解决方案来引导我们学习更多的shell命令行下时间的管理策略,以及了解这些管理方法在不同shell间的差别。
问题描述
在流中添加时间戳是一个挑战linux教程下载,由于没有标准工具可以完成这个任务。你可以安装针对此目的专门设计的工具(比如moreutils中的ts,或则daemontools中的multilog),或则使用某种编程语言编撰过滤器。理想情况下,你不希望为每一行输入日志都创建一个date(1)命令的进程,由于这样太慢了。你应当使用外置工具。旧版本的Bash未能完成这个任务。你起码须要Bash4.2版本能够使用printf%(...)T选项。否则,你可以使用Perl、Python、Tcl等编撰程序来读取行并带有时间戳写出。
来尝试解决这个问题
在选择程序或编撰函数(我们称之为stamper)以后,你只须要将流通过它进行管线传输:
./myprogram 2>&1 | stamper > logfile
下边是一个简单的使用Bash4.2的时间戳生成程序,以秒为单位进行精确度:
# Bash 4.2 or later
stamper() {
while read -r; do
printf '%(%Y-%m-%d %H:%M:%S)T %sn' -1 "$REPLY"
done
}
在支持strftime(3)中的%s的系统上,你还可以使用%(%s)T来世成Unix时间戳。但遗憾的是,这些方式不是可移植的。
Bash5.0添加了EPOCHSECONDS(1秒精度)和EPOCHREALTIME(毫秒精度)变量,可以取代(或与之一齐使用)printf%()T:
# Bash 5.0 or later
stamper() {
while read -r; do
printf '%s %sn' "$EPOCHREALTIME" "$REPLY"
done
}
还有一个专门因此目的设计的外部工具是moreutils包中的ts。它的默认格式选择不当(采用人类可读的月份名称,以MmmDDYYYY格式显示),但你可以指定一个格式。它支持毫秒精度。
stamper() {
ts "%Y-%m-%d %H:%M:%S.%.S"
}
另一个选择是multilog,它可以提供毫秒级精度:
stamper() {
multilog t e 2>&1
}
multilog的-e选项告诉它将每一行写入stderr,而不是手动轮转目录中的文件。multilog使用TAI6464N格式写入时间戳,类似于Unixepoch格式。你可以使用转换工具(比如tai64nlocal)将其转换为可读格式。
以下是使用Tcl的示例,精度为毫秒:
#!/usr/bin/env tclsh
while {[gets stdin line] >= 0} {
set now [clock microseconds]
set frac [expr {$now % 1000000}]
set now [expr {$now / 1000000}]
puts "[clock format $now -format {%Y-%m-%d %H:%M:%S}].$frac $line"
}
这最好作为一个单独的、独立的脚本来使用。把它嵌入到bash函数中也是可以的,虽然很难看:
stamper() {
tclsh <(cat <= 0} {
set now [clock microseconds]
set frac [expr {$now % 1000000}]
set now [expr {$now / 1000000}]
puts "[clock format $now -format {%Y-%m-%d %H:%M:%S}].$frac $line"
}
EOF
)
}
对于Perl爱好者unix时间戳转换工具,以下是一个精度为一秒的示例:
stamper() {
perl -p -e '$|=1; @l=localtime; printf "d-d-d d:d:d ",
1900+$l[5], $l[4], $l[3], $l[2], $l[1], $l[0]'
}
从上述选择中选择一个linux 虚拟主机,或则用你喜欢的编程语言编撰另一个(或更好的)。
现今,在这一点上,一些读者可能要问怎么在记录带有时间戳的流到文件的同时查看未更改的流。假定她们不完全疯狂,所以她们不关心stdout和stderr的分离。她们只想要将所有内容根据次序混和在一个文件中,包括stdout和stderr。
如今,运行脚本使其输出和stderr正常显示到屏幕,将带有时间戳的行写入日志文件相对容易:
./myscript 2>&1 | tee >(stamper >logfile)
将同样的内容装入一个脚本中稍稍复杂一些:
#!/bin/bash
exec > >(tee >(stamper >logfile)) 2>&1
while sleep 0.2s; do
echo "TPS report $RANDOM"
done
其实,假如脚本中写入stamper的部份使用stdout缓冲区unix时间戳转换工具,可能会存在缓冲问题。请考虑这一点。
我不在意速率有多慢!我希望每秒调用date(1)一千次!我的笔记本可以当成加热器使用!
这是低效和可怕的。
stamper() {
while read -r; do
printf '%s %sn' "$(date +'%Y-%m-%d %H:%M:%S')" "$REPLY"
done
}
PS:请不要使用这些技巧。这是荒谬的。
More
假如你想学习怎么编撰愈发强壮和可靠的Shell脚本,降低生产环境中的错误和故障,这么关注我吧!我会分享Shell编程的最佳实践和建议,帮助你提升Shell脚本的鲁棒性和可维护性。假如你想深入了解Shell编程的实际应用和方法,可以关注我的《Shell脚本编程最佳实践》专栏,上面有我在一线互联网大厂的实际生产经验和最佳实践,帮助你高效完成各类手动化任务。