是否有一个 Unix 实用程序来为 stdin 添加时间戳?

最后,我用 Python 为此编写了一个小脚本,但我想知道是否有一个实用程序可以将文本输入到其中,在每一行前面加上一些文本——在我的特定情况下,是一个时间戳。理想情况下,使用类似于:

cat somefile.txt | prepend-timestamp

(在你回答之前,我试了这个:

cat somefile.txt | sed "s/^/`date`/"

但是当 sed 执行时,它只计算 date 命令一次,因此在每一行前面添加相同的时间戳是不正确的。)

77236 次浏览

这个怎么样?

cat somefile.txt | perl -pne 'print scalar(localtime()), " ";'

从您希望获得实时时间戳来判断,也许您希望对日志文件或其他内容进行实时更新?也许吧

tail -f /path/to/log | perl -pne 'print scalar(localtime()), " ";' > /path/to/log-with-timestamps

如果每一行上的值都是相同的,那么使用文件启动 emacs,然后:

Ctrl + <space>

在文件的开头(标记那个点) ,然后向下滚动到最后一行的开头(Alt + > 会到文件的末尾... 这可能也会涉及到 Shift 键,然后 Ctrl + a 会到那一行的开头)和:

Ctrl + x r t

它是要插入到刚才指定的矩形(宽度为0的矩形)的命令。

2008-8-216:45 PM < enter >

或者任何你想添加的东西... 然后你会看到文本被添加到0宽度的矩形内的每一行。

更新: 我刚刚意识到你不想要相同的日期,所以这不会工作... 虽然你可以做到这一点,在 emacs 与一个稍微复杂的自定义宏,但仍然,这种矩形编辑是非常好的了解..。

我不喜欢 Unix 但我觉得你可以用

gawk '{print strftime("%d/%m/%y",systime()) $0 }' < somefile.txt

可以尝试使用 awk:

<command> | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; fflush(); }'

您可能需要确保 <command>产生行缓冲输出,即在每行之后刷新其输出流; awk添加的时间戳将是行的末尾出现在其输入管道上的时间。

如果 awk 显示错误,则尝试使用 gawk

Kieron 的回答是目前为止最好的。如果你有问题,因为第一个程序正在缓冲它的出口,你可以使用 unbuffer 程序:

unbuffer <command> | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; }'

它默认安装在大多数 Linux 系统上。如果您需要自己构建它,那么它是期望包的一部分

Http://expect.nist.gov

注释 ,可以通过该链接获得,也可以作为 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

使用 read (1)命令从标准输入一次读取一行,然后使用 date (1)以您选择的格式输出日期前面的行。

$ cat timestamp
#!/bin/sh
while read line
do
echo `date` $line
done
$ cat somefile.txt | ./timestamp
#! /bin/sh
unbuffer "$@" | perl -e '
use Time::HiRes (gettimeofday);
while(<>) {
($s,$ms) = gettimeofday();
print $s . "." . $ms . " " . $_;
}'

下面是我的 awk 解决方案(来自安装在 C: bin 目录中的 MKS Tools 的 Windows/XP 系统)。它的设计目的是将当前日期和时间以 mm/dd hh: mm 的格式添加到每行的开头,并在读取每行时从系统获取该时间戳。当然,您可以使用 BEGIN 模式获取一次时间戳,然后将该时间戳添加到每条记录(都是相同的)。我这样做是为了在生成日志消息时用时间戳标记生成给 stdout 的日志文件。

/"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 变量配置正确)。

我就直说了吧 Daemontools中有两个实用程序叫做 TA64N本地它们用来预置时间戳来记录消息。

例如:

cat file | tai64n | tai64nlocal

来自 Moreutilsts将为您提供的每一行输入添加一个时间戳。您也可以使用 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

Caerwyn 的答案可以作为一个子例程运行,这样就可以防止每行的新进程:

timestamp(){
while read line
do
echo `date` $line
done
}


echo testing 123 |timestamp

从最简单的答案中提炼出给定的答案:

unbuffer $COMMAND | ts

在 Ubuntu 上,它们来自 Expect-dev 和 Moreutils 包。

sudo apt-get install expect-dev moreutils

在 OSX 上和 datetrxargs一起做:

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 \"{}\"'"

混合以上来自 Natevw和弗兰克 · C · 艾格勒的一些答案。

它的执行时间为毫秒,比每次调用外部 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);
}'

免责声明 : 我提出的解决方案不是 Unix 内置的实用程序。

几天前我也遇到过类似的问题。我不喜欢上述解决方案的语法和局限性,所以我很快在 Go 中组装了一个程序来完成这项工作。

你可以在这里检查工具: 翻译: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对: 奇芳校对

在 GitHub 项目的 释放部分中有针对 Linux、 MacOS 和 Windows 的预构建可执行文件。

该工具可以处理不完整的输出行,并且(从我的角度来看)具有更紧凑的语法。

<command> | preftime

虽然不是很理想,但我觉得还是分享一下吧,万一对谁有帮助呢。

$ cat somefile.txt | sed "s/^/`date`/"

你可以这样做(使用 (咒语)) :

$ 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即可。

其他答案大多有效,但也有一些缺点。特别是:

  1. 许多程序需要安装一个在 linux 系统中不常见的命令,这可能是不可能或不方便的。
  2. 因为它们使用管道,所以不会在 stderr 上添加时间戳,并且会丢失退出状态。
  3. 如果对 stderr 和 stdout 使用多个管道,那么有些管道没有原子打印,从而导致混合的输出行,如 [timestamp] [timestamp] stdout line \nstderr line
  4. 缓冲可能会导致问题,而 unbuffer需要额外的依赖项。

要解决(4) ,我们可以使用 stdbuf -i0 -o0 -e0,它通常在大多数 Linux 系统上可用(参见 如何使任何 shell 命令的输出不受缓冲?)。

要解决(3) ,您只需要小心地一次打印整个行。

  • 坏: ruby -pe 'print Time.now.strftime(\"[%Y-%m-%d %H:%M:%S] \")'(打印时间戳,然后打印 $_的内容。)
  • 好: ruby -pe '\$_ = Time.now.strftime(\"[%Y-%m-%d %H:%M:%S] \") + \$_'(修改 $_,然后打印出来。)

要解决(2) ,我们需要使用多个管道并保存退出状态:

alias tslines-pipe="stdbuf -i0 -o0 ruby -pe '\$_ = Time.now.strftime(\"[%Y-%m-%d %H:%M:%S] \") + \$_'"
function tslines() (
stdbuf -o0 -e0 "$@" 2> >(tslines-pipe) > >(tslines-pipe)
status="$?"
exit $status
)

然后可以用 tslines some command --options运行命令。

这个 差不多可以正常工作,只是有时某个管道的退出时间稍长,而且 tslines函数已经退出,因此下一个提示符已经打印出来了。例如,这个命令似乎在下一行的提示符出现之后打印所有输出,这可能有点令人困惑:

tslines bash -c '(for (( i=1; i<=20; i++ )); do echo stderr 1>&2; echo stdout;  done)'

这两个管道过程与 Tslines 函数之间需要有一定的协调方法。大概有很多方法可以做到这一点。我发现的一种方法是让管道向一个管道发送一些行,让 main 函数可以监听,并且只退出 之后,它从两个管道处理程序接收数据。综上所述:

alias tslines-pipe="stdbuf -i0 -o0 ruby -pe '\$_ = Time.now.strftime(\"[%Y-%m-%d %H:%M:%S] \") + \$_'"
function tslines() (
# Pick a random name for the pipe to prevent collisions.
pipe="/tmp/pipe-$RANDOM"
  

# Ensure the pipe gets deleted when the method exits.
trap "rm -f $pipe" EXIT


# Create the pipe.  See https://www.linuxjournal.com/content/using-named-pipes-fifos-bash
mkfifo "$pipe"
# echo will block until the pipe is read.
stdbuf -o0 -e0 "$@" 2> >(tslines-pipe; echo "done" >> $pipe) > >(tslines-pipe; echo "done" >> $pipe)
status="$?"


# Wait until we've received data from both pipe commands before exiting.
linecount=0
while [[ $linecount -lt 2 ]]; do
read line
if [[ "$line" == "done" ]]; then
((linecount++))
fi
done < "$pipe"
exit $status
)

同步机制感觉有点复杂; 希望有更简单的方法来实现它。

不需要指定 strftime()中的所有参数,除非你真的想自定义输出格式:

   echo "abc 123 xyz\njan 765 feb" \
\
| gawk -Sbe 'BEGIN {_=strftime()" "} sub("^",_)'


Sat Apr  9 13:14:53 EDT 2022 abc 123 xyz
Sat Apr  9 13:14:53 EDT 2022 jan 765 feb

如果你有 mawk 1.3.4.也是一样的,即使是在 awk-没有时间特征的变体上,一个快速的 getline 也可以模仿它:

echo "abc 123 xyz\njan 765 feb" \
\
| mawk2 'BEGIN {     (__="date")|getline _;
close(__)
_=_" " } sub("^",_)'
   

Sat Apr  9 13:19:38 EDT 2022 abc 123 xyz
Sat Apr  9 13:19:38 EDT 2022 jan 765 feb

如果您想跳过所有 getline 和 BEGIN {} ,那么可以这样做:

mawk2 'sub("^",_" ")' \_="$(date)"