Is there a Unix utility to prepend timestamps to stdin?

Viewed 76883

I ended up writing a quick little script for this in Python, but I was wondering if there was a utility you could feed text into which would prepend each line with some text -- in my specific case, a timestamp. Ideally, the use would be something like:

cat somefile.txt | prepend-timestamp

(Before you answer sed, I tried this:

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

But that only evaluates the date command once when sed is executed, so the same timestamp is incorrectly prepended to each line.)

19 Answers

ts from moreutils will prepend a timestamp to every line of input you give it. You can format it using strftime too.

$ 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
$ 

To install it:

sudo apt-get install moreutils

Could try using awk:

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

You may need to make sure that <command> produces line buffered output, i.e. it flushes its output stream after each line; the timestamp awk adds will be the time that the end of the line appeared on its input pipe.

If awk shows errors, then try gawk instead.

annotate, available via that link or as annotate-output in the Debian devscripts package.

$ 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

How about this?

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

Judging from your desire to get live timestamps, maybe you want to do live updating on a log file or something? Maybe

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

Kieron's answer is the best one so far. If you have problems because the first program is buffering its out you can use the unbuffer program:

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

It's installed by default on most linux systems. If you need to build it yourself it is part of the expect package

http://expect.nist.gov

Just gonna throw this out there: there are a pair of utilities in daemontools called tai64n and tai64nlocal that are made for prepending timestamps to log messages.

Example:

cat file | tai64n | tai64nlocal

Use the read(1) command to read one line at a time from standard input, then output the line prepended with the date in the format of your choosing using date(1).

$ cat timestamp
#!/bin/sh
while read line
do
  echo `date` $line
done
$ cat somefile.txt | ./timestamp

I'm not an Unix guy, but I think you can use

gawk '{print strftime("%d/%m/%y",systime()) $0 }' < somefile.txt
#! /bin/sh
unbuffer "$@" | perl -e '
use Time::HiRes (gettimeofday);
while(<>) {
        ($s,$ms) = gettimeofday();
        print $s . "." . $ms . " " . $_;
}'

The other answers mostly work, but have some drawbacks. In particular:

  1. Many require installing a command not commonly found on linux systems, which may not be possible or convenient.
  2. Since they use pipes, they don't put timestamps on stderr, and lose the exit status.
  3. If you use multiple pipes for stderr and stdout, then some do not have atomic printing, leading to intermingled lines of output like [timestamp] [timestamp] stdout line \nstderr line
  4. Buffering can cause problems, and unbuffer requires an extra dependency.

To solve (4), we can use stdbuf -i0 -o0 -e0 which is generally available on most linux systems (see How to make output of any shell command unbuffered?).

To solve (3), you just need to be careful to print the entire line at a time.

  • Bad: ruby -pe 'print Time.now.strftime(\"[%Y-%m-%d %H:%M:%S] \")' (Prints the timestamp, then prints the contents of $_.)
  • Good: ruby -pe '\$_ = Time.now.strftime(\"[%Y-%m-%d %H:%M:%S] \") + \$_' (Alters $_, then prints it.)

To solve (2), we need to use multiple pipes and save the exit status:

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
)

Then you can run a command with tslines some command --options.

This almost works, except sometimes one of the pipes takes slightly longer to exit and the tslines function has exited, so the next prompt has printed. For example, this command seems to print all the output after the prompt for the next line has appeared, which can be a bit confusing:

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

There needs to be some coordination method between the two pipe processes and the tslines function. There are presumably many ways to do this. One way I found is to have the pipes send some lines to a pipe that the main function can listen to, and only exit after it's received data from both pipe handlers. Putting that together:

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
)

That synchronization mechanism feels a bit convoluted; hopefully there's a simpler way to do it.

No need to specify all the parameters in strftime() unless you really want to customize the outputting format :

   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

works the same if you have mawk 1.3.4. Even on awk-variants without the time features, a quick getline could emulate it :

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

If you wanna skip all that getline and BEGIN { }, then something like this :

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

If the value you are prepending is the same on every line, fire up emacs with the file, then:

Ctrl + <space>

at the beginning of the of the file (to mark that spot), then scroll down to the beginning of the last line (Alt + > will go to the end of file... which probably will involve the Shift key too, then Ctrl + a to go to the beginning of that line) and:

Ctrl + x r t

Which is the command to insert at the rectangle you just specified (a rectangle of 0 width).

2008-8-21 6:45PM <enter>

Or whatever you want to prepend... then you will see that text prepended to every line within the 0 width rectangle.

UPDATE: I just realized you don't want the SAME date, so this won't work... though you may be able to do this in emacs with a slightly more complicated custom macro, but still, this kind of rectangle editing is pretty nice to know about...

Related