是否有 Unix 实用程序可以将时间戳添加到标准输入?
Posted
技术标签:
【中文标题】是否有 Unix 实用程序可以将时间戳添加到标准输入?【英文标题】:Is there a Unix utility to prepend timestamps to stdin? 【发布时间】:2010-09-06 11:59:44 【问题描述】:我最终在 Python 中为此编写了一个快速的小脚本,但我想知道是否有一个实用程序可以输入文本,它会在每行前面加上一些文本——在我的具体情况下,是时间戳。理想情况下,使用类似于:
cat somefile.txt | prepend-timestamp
(在你回答 sed 之前,我试过这个:
cat somefile.txt | sed "s/^/`date`/"
但是在执行 sed 时只计算一次 date 命令,所以相同的时间戳被错误地添加到每一行。)
【问题讨论】:
cat somefile.txt
是不是有点“误导”?我希望它“立即”发生并有一个时间戳。这不是更好的测试程序吗:(echo a; sleep 1; echo b; sleep 3; echo c; sleep 2)
?
看起来没有一个答案能轻易给我几毫秒(我在 Mac 上)
【参考方案1】:
moreutils 中的ts
将为您提供的每一行输入添加一个时间戳。您也可以使用 strftime 对其进行格式化。
$ echo 'foo bar baz' | ts
Mar 21 18:07:28 foo bar baz
$ echo 'blah blah blah' | ts '%F %T'
2012-03-21 18:07:30 blah blah blah
$
要安装它:
sudo apt-get install moreutils
【讨论】:
同时捕获和标记stderr:bad command 2>&1 | ts
导致Jan 29 19:58:31 -bash: bad: command not found
我想使用时间格式2014 Mar 21 18:07:28
。我如何获得它?
@becko - 根据manual page,给出strftime
格式字符串作为参数:例如[.. command ..] | ts '%Y %b %d %T'
。
对于 OS X,brew install moreutils
。要输出 UTC,您可以在本地设置 TZ,例如ls -l |TZ=UTC ts '%Y-%m-%dT%H:%M:%SZ'
@Dorian 这是因为 ruby 正在缓冲输出;如果你在睡觉前刷新缓冲区,它就足够了:ruby -e "puts 1; STDOUT.flush; sleep 1; puts 2; STDOUT.flush; sleep 2; puts 3" | ts '%F %T'
【参考方案2】:
可以尝试使用awk
:
<command> | awk ' print strftime("%Y-%m-%d %H:%M:%S"), $0; fflush(); '
您可能需要确保<command>
产生行缓冲输出,即它在每一行之后刷新其输出流; awk
添加的时间戳将是行尾出现在其输入管道上的时间。
如果 awk 显示错误,请尝试 gawk
。
【讨论】:
在我的系统上,'awk' 本身正在缓冲。 (这对于日志文件可能有问题)。我使用以下方法解决了这个问题: awk ' print strftime("%Y-%m-%dT%H:%M:%S"), $0; fflush(); ' strftime() 似乎是 GNU awk 扩展,因此,例如,如果您使用的是 Mac OS,请使用 gawk 而不是 awk。 bash 的强大功能让我感到惊讶。我没想到这个复杂的任务会有这么简单的解决方案。 对于 OS X 用户,您必须安装gawk
并使用它而不是 awk
。如果你有 MacPorts:sudo port install gawk
@teh_senaus 据我所知,awk
的strftime()
没有毫秒精度。但是,您可以使用date
命令。基本上你只需将纳秒修剪为三个字符。它看起来像这样:COMMAND | while read -r line; do echo "$(date '+%Y-%m-%d %T.%3N') $line"; done
。请注意,您可以将 %H:%M:%S 缩写为 %T。如果您出于某种原因仍想使用awk
,可以执行以下操作:COMMAND | awk ' "date +%Y-%m-%d\\ %T.%3N" | getline timestamp; print timestamp, $0; fflush(); '
【参考方案3】:
annotate,可通过该链接或 Debian devscripts
软件包中的 annotate-output
获得。
$ echo -e "a\nb\nc" > lines
$ annotate-output cat lines
17:00:47 I: Started cat lines
17:00:47 O: a
17:00:47 O: b
17:00:47 O: c
17:00:47 I: Finished with exitcode 0
【讨论】:
这是一个很好的解决方案,但不适用于管道输出或 subshelled 输出。它只会重新格式化您作为参数提供的程序或脚本的输出。 annotate-output 运行良好且易于使用,但速度真的很慢。【参考方案4】:将给定的答案提炼成最简单的答案:
unbuffer $COMMAND | ts
在 Ubuntu 上,它们来自 expect-dev 和 moreutils 包。
sudo apt-get install expect-dev moreutils
【讨论】:
它是expect
,而不是expect-dev
,但后者拉入了前者,所以这行得通。 (Ubuntu 14.10,我想知道二进制文件是否在某个时候被移动到了不同的包中。)
对于 Ubuntu 14.04 LTS,它仍然在 expect-dev
如果您想获得毫秒精度的经过时间,请使用unbuffer $COMMAND | ts -s %.s
(-s
表示经过的时间,%.s
表示以秒为单位的小数部分)【参考方案5】:
这个怎么样?
cat somefile.txt | perl -pne 'print scalar(localtime()), " ";'
从您希望获得实时时间戳来看,也许您想对日志文件或其他内容进行实时更新?也许
tail -f /path/to/log | perl -pne 'print scalar(localtime()), " ";' > /path/to/log-with-timestamps
【讨论】:
这个 perl 示例对我特别有用,因为我作为 ubuntu 部署的用户工作,它显然使用 'mawk' 而不是 'gawk' -- 并且没有 strftime 功能。谢谢! +1 这在默认的 Ubuntu 安装上很方便。我使用tail -f /path/to/log | perl -pne 'use POSIX qw(strftime); print strftime "%Y-%m-%dT%H:%M:%SZ ", gmtime();'
来获取 ISO8601/RFC3339 样式的日期,而不是更简单的版本产生的古怪日期。
如果您希望能够在到达时查看带时间戳的日志输出,在 print 语句之前添加 BEGIN $|++;
可能会有所帮助,以便让 Perl 在每一行刷新其输出。跨度>
您可以将 perl 脚本进一步缩短为 ls | perl -pne 'print localtime." "'
。标量转换是由于字符串连接而发生的。
为什么同时使用-p
和-n
选项?如果您指定了-p
,那么-n
似乎是多余的。【参考方案6】:
Kieron 的答案是迄今为止最好的答案。如果您因为第一个程序正在缓冲而遇到问题,您可以使用 unbuffer 程序:
unbuffer <command> | awk ' print strftime("%Y-%m-%d %H:%M:%S"), $0; '
它默认安装在大多数 linux 系统上。如果您需要自己构建它,它是期望包的一部分
http://expect.nist.gov
【讨论】:
请注意,我发现 'unbuffer' 有时会隐藏被调用程序的返回结果——所以它在 Jenkins 或其他依赖返回状态的东西中没有用【参考方案7】:只是把它扔在那里:daemontools 中有一对实用程序,称为 tai64n 和 tai64nlocal,用于在记录消息之前添加时间戳。
例子:
cat file | tai64n | tai64nlocal
【讨论】:
【参考方案8】:使用 read(1) 命令从标准输入中一次读取一行,然后以您使用 date(1) 选择的格式输出前面带有日期的行。
$ cat timestamp
#!/bin/sh
while read line
do
echo `date` $line
done
$ cat somefile.txt | ./timestamp
【讨论】:
有点重量级,因为它每行分叉一个新进程,但它有效!【参考方案9】:我不是 Unix 人,但我认为你可以使用
gawk 'print strftime("%d/%m/%y",systime()) $0 ' < somefile.txt
【讨论】:
【参考方案10】:#! /bin/sh
unbuffer "$@" | perl -e '
use Time::HiRes (gettimeofday);
while(<>)
($s,$ms) = gettimeofday();
print $s . "." . $ms . " " . $_;
'
【讨论】:
【参考方案11】:$ cat somefile.txt | sed "s/^/`date`/"
你可以这样做(使用 gnu/sed):
$ some-command | sed "x;s/.*/date +%T/e;G;s/\n/ /g"
示例:
$ echo 'line1'; sleep 2; echo 'line2'; | sed "x;s/.*/date +%T/e;G;s/\n/ /g"
20:24:22 line1
20:24:24 line2
当然,您可以使用程序的其他选项日期。只需将date +%T
替换为您需要的即可。
【讨论】:
【参考方案12】:这是我的 awk 解决方案(来自安装在 C:\bin 目录中的 MKS 工具的 Windows/XP 系统)。它旨在以 mm/dd hh:mm 的形式将当前日期和时间添加到每行的开头,并在读取每一行时从系统中获取该时间戳。当然,您可以使用 BEGIN 模式获取一次时间戳并将该时间戳添加到每条记录(都一样)。我这样做是为了用生成日志消息时的时间戳标记正在生成到标准输出的日志文件。
/"pattern"/ "C\:\\\\bin\\\\date '+%m/%d %R'" | getline timestamp;
print timestamp, $0;
其中“pattern”是要在输入行中匹配的字符串或正则表达式(不带引号),如果您希望匹配所有输入行,则为可选。
这也应该适用于 Linux/UNIX 系统,只需去掉 C\:\\bin\\ 离开行
"date '+%m/%d %R'" | getline timestamp;
当然,这假设命令“date”将您带到标准 Linux/UNIX 日期显示/设置命令,而无需特定路径信息(即,您的环境 PATH 变量已正确配置)。
【讨论】:
在 OS X 上,我需要在 date 命令之前留一个空格。可悲的是,它似乎没有重新评估每一行的命令。就像其他人建议的那样,我正在尝试向 tail -f 添加时间戳。【参考方案13】:上面的一些答案来自natevw 和 Frank Ch。艾格勒。
它有毫秒级,每次都比调用外部date
命令执行得更好,并且在大多数服务器中都可以找到perl。
tail -f log | perl -pne '
use Time::HiRes (gettimeofday);
use POSIX qw(strftime);
($s,$ms) = gettimeofday();
print strftime "%Y-%m-%dT%H:%M:%S+$ms ", gmtime($s);
'
带有刷新和循环读取的替代版本:
tail -f log | perl -pne '
use Time::HiRes (gettimeofday); use POSIX qw(strftime);
$|=1;
while(<>)
($s,$ms) = gettimeofday();
print strftime "%Y-%m-%dT%H:%M:%S+$ms $_", gmtime($s);
'
【讨论】:
上述两个缺陷: 1. $ms 未对齐和零填充。 2. 日志中的任何 % 代码都将被破坏。因此,我没有使用打印行,而是使用:printf "%s+%06d %s", (strftime "%Y-%m-%dT%H:%M:%S", gmtime($s)), $ms, $_;
【参考方案14】:
caerwyn 的答案可以作为子程序运行,这将阻止每行的新进程:
timestamp()
while read line
do
echo `date` $line
done
echo testing 123 |timestamp
【讨论】:
如何防止date
在每一行生成?
@Gyom 它不会阻止每一行生成日期。它可以防止 shell 生成每一行。我会在 bash 中补充一点,您可以在源代码中执行以下操作:timestamp() while read line; do echo "$(date) $line"; done; ; export -f timestamp
。
将日期作为子进程调用仍然意味着为每一行生成它,但是您首先启动脚本。
为了防止每行生成命令date
,请尝试使用带有 %(datespec)T 格式的内置 bash printf
。所以它可能看起来像timestamp() while IFS= read -r line; do printf "%(%F %T)T %s\n" -1 "$line"; done;
。外壳内置程序不会产生。 Datespec 格式类似于strftime(3)
即date
格式。这需要 bash,不确定从哪个版本支持 printf
说明符。【参考方案15】:
免责声明:我提出的解决方案不是 Unix 内置实用程序。
几天前我遇到了类似的问题。我不喜欢上述解决方案的语法和限制,所以我很快在 Go 中编写了一个程序来为我完成这项工作。
您可以在此处查看该工具:preftime
在 GitHub 项目的 Releases 部分中有适用于 Linux、MacOS 和 Windows 的预构建可执行文件。
该工具可以处理不完整的输出行,并且(在我看来)具有更紧凑的语法。
<command> | preftime
这并不理想,但我会分享它以防它对某人有所帮助。
【讨论】:
【参考方案16】:在 OSX 上使用 date
和 tr
和 xargs
:
alias predate="xargs -I sh -c 'date +\"%Y-%m-%d %H:%M:%S\" | tr \"\n\" \" \"; echo \"\"'"
<command> | predate
如果你想要毫秒:
alias predate="xargs -I sh -c 'date +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"\"'"
但请注意,在 OSX 上,date 并没有为您提供 %N 选项,因此您需要安装 gdate (brew install coreutils
) 并最终达到此目的:
alias predate="xargs -I sh -c 'gdate +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"\"'"
【讨论】:
【参考方案17】:如果您在每一行添加的值都相同,请使用该文件启动 emacs,然后:
Ctrl +
在文件的开头(标记该位置),然后向下滚动到最后一行的开头(Alt + > 将转到文件末尾...这可能也涉及到 Shift 键,然后 Ctrl + a 转到该行的开头)和:
Ctrl + x r t
在您刚刚指定的矩形(宽度为 0 的矩形)处插入的命令是什么。
2008-8-21 下午 6:45
或者任何你想添加的东西......然后你会看到该文本添加到 0 宽度矩形内的每一行。
更新:我刚刚意识到你不想要相同的日期,所以这行不通......虽然你可以在 emacs 中使用稍微复杂的自定义宏来做到这一点,但仍然是这种矩形编辑很高兴知道...
【讨论】:
以上是关于是否有 Unix 实用程序可以将时间戳添加到标准输入?的主要内容,如果未能解决你的问题,请参考以下文章