Is there a Unix utility to prepend timestamps to stdin?

UnixShellAwk

Unix Problem Overview


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.)

Unix Solutions


Solution 1 - Unix

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

Solution 2 - Unix

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.

Solution 3 - Unix

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

Solution 4 - Unix

Distilling the given answers to the simplest one possible:

unbuffer $COMMAND | ts

On Ubuntu, they come from the expect-dev and moreutils packages.

sudo apt-get install expect-dev moreutils

Solution 5 - Unix

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

Solution 6 - Unix

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>

Solution 7 - Unix

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

Solution 8 - Unix

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

Solution 9 - Unix

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

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

Solution 10 - Unix

#! /bin/sh
unbuffer "$@" | perl -e '
use Time::HiRes (gettimeofday);
while(<>) {
        ($s,$ms) = gettimeofday();
        print $s . "." . $ms . " " . $_;
}'

Solution 11 - Unix

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

you can do this (with gnu/sed):

$ some-command | sed "x;s/.*/date +%T/e;G;s/\n/ /g"

example:

$ { echo 'line1'; sleep 2; echo 'line2'; } | sed "x;s/.*/date +%T/e;G;s/\n/ /g"
20:24:22 line1
20:24:24 line2

of course, you can use other options of the program date. just replace date +%T with what you need.

Solution 12 - Unix

Here's my awk solution (from a Windows/XP system with MKS Tools installed in the C:\bin directory). It is designed to add the current date and time in the form mm/dd hh:mm to the beginning of each line having fetched that timestamp from the system as each line is read. You could, of course, use the BEGIN pattern to fetch the timestamp once and add that timestamp to each record (all the same). I did this to tag a log file that was being generated to stdout with the timestamp at the time the log message was generated.

/"pattern"/ "C\:\\\\bin\\\\date '+%m/%d %R'" | getline timestamp;
print timestamp, $0;

where "pattern" is a string or regex (without the quotes) to be matched in the input line, and is optional if you wish to match all input lines.

This should work on Linux/UNIX systems as well, just get rid of the C:\\bin\\ leaving the line

             "date '+%m/%d %R'" | getline timestamp;

This, of course, assumes that the command "date" gets you to the standard Linux/UNIX date display/set command without specific path information (that is, your environment PATH variable is correctly configured).

Solution 13 - Unix

Mixing some answers above from natevw and Frank Ch. Eigler.

It has milliseconds, performs better than calling a external date command each time and perl can be found in most of the servers.

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);
  '

Alternative version with flush and read in a loop:

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);
  }'

Solution 14 - Unix

caerwyn's answer can be run as a subroutine, which would prevent the new processes per line:

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

echo testing 123 |timestamp

Solution 15 - Unix

Disclaimer: the solution I am proposing is not a Unix built-in utility.

I faced a similar problem a few days ago. I did not like the syntax and limitations of the solutions above, so I quickly put together a program in Go to do the job for me.

You can check the tool here: preftime

There are prebuilt executables for Linux, MacOS, and Windows in the Releases section of the GitHub project.

The tool handles incomplete output lines and has (from my point of view) a more compact syntax.

<command> | preftime

It's not ideal, but I though I'd share it in case it helps someone.

Solution 16 - Unix

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 https://stackoverflow.com/questions/3465619/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.

Solution 17 - Unix

doing it with date and tr and xargs on OSX:

alias predate="xargs -I{} sh -c 'date +\"%Y-%m-%d %H:%M:%S\" | tr \"\n\" \" \"; echo \"{}\"'"
<command> | predate

if you want milliseconds:

alias predate="xargs -I{} sh -c 'date +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"{}\"'"

but note that on OSX, date doesn't give you the %N option, so you'll need to install gdate (brew install coreutils) and so finally arrive at this:

alias predate="xargs -I{} sh -c 'gdate +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"{}\"'"

Solution 18 - Unix

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)"

Solution 19 - Unix

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJoe ShawView Question on Stackoverflow
Solution 1 - UnixMark McKinstryView Answer on Stackoverflow
Solution 2 - UnixKieronView Answer on Stackoverflow
Solution 3 - UnixT PercivalView Answer on Stackoverflow
Solution 4 - UnixWillemView Answer on Stackoverflow
Solution 5 - Unixjj33View Answer on Stackoverflow
Solution 6 - UnixMark HarrisonView Answer on Stackoverflow
Solution 7 - UnixchazomaticusView Answer on Stackoverflow
Solution 8 - UnixcaerwynView Answer on Stackoverflow
Solution 9 - UnixPabloGView Answer on Stackoverflow
Solution 10 - UnixFrank Ch. EiglerView Answer on Stackoverflow
Solution 11 - Unixaleksandr barakinView Answer on Stackoverflow
Solution 12 - UnixElGringoGeekView Answer on Stackoverflow
Solution 13 - UnixKeymonView Answer on Stackoverflow
Solution 14 - UnixcrumplecrapView Answer on Stackoverflow
Solution 15 - UnixMomchil AtanasovView Answer on Stackoverflow
Solution 16 - UnixLucas WimanView Answer on Stackoverflow
Solution 17 - Unixorion elenzilView Answer on Stackoverflow
Solution 18 - UnixRARE Kpop ManifestoView Answer on Stackoverflow
Solution 19 - UnixMike StoneView Answer on Stackoverflow