19

I'm lazy and I could write a script to do this, but I'm even too lazy to think of how to do it.

I often do things like :

cris$ python runexperiment.py > output.txt
cris$ cat output.txt

Sometimes when looking at the long output of an experiment I like to let the page just scroll and watch the successive patterns form and disperse. But using cat on a file with 1 million lines finishes in maybe 5 seconds. This is too quick even for me.

Is there any way that I can slow down the speed of viewing the file, something like a 'scroll utility'? I want fast, but not 200k lines a second (all of which presumably the display would never even register anyway).

Something like

cris$ scroll -lps=300 output.txt

And then sitting back and watching 300 lines per second roll past would be ideal, I imagine.

2
  • 8
    Try something like cat FILENAME | pv -l -L 900 -q. The limit is in bytes per second, not lines per second, so I'm making this a comment not an answer. Dec 31 '12 at 7:54
  • Ok well that is a cool utility, and that works in part. But yes, it is a little choppy since it goes after bps not lps. Dec 31 '12 at 8:03
23

Short and readable:

perl -pe "system 'sleep .003'" log.txt

I post this solutions because they are small and readable, as comments of DMas's answer seem promote this kind of solution!

But I hate this because: For this run, perl will fork to /bin/sleep 300x / seconds!

This is a big ressource consumer! Also a wrong good solutions!!

Using builtin sleep in

Unfortunely, builtin sleep is limited to integers. So we have to use select instead:

perl -e 'print && select undef,undef,undef,.00333 while <>;'

Under perl, print while <> could be replaced by the -p switch:

perl -pe 'select undef,undef,undef,.00333'

Let's try:

time /bin/ls -l /usr/bin | perl -pe 'select undef,undef,undef,.00333' | wc
   2667   24902  171131

real    0m9.173s
user    0m0.056s
sys     0m0.048s

bc -l < <(echo 2667/9.173)
290.74457647443584432573

Explanation:

  • 300 lines / sec means 1 line by 0.0033333333 secs.

  • print without argument prints $_ which is default input space.

  • called as ... | perl -e, ... | perl -ne or ... | perl -pe, standard input would be automaticaly assigned to *STDIN which is default file descriptor, so <> would do the same as <STDIN> which will read from standard input until $/ (input record separator which is by default a newline) will be reached. In English, by default <> will read one line from standard input and assign content to $_ variable.

  • && is an and condition, but is used there as a chain command separator so after (successfully) print one line, doing next command.

  • select is a programmer's trick to not use sleep. This command is designed to trap events on file descriptors (inputs and/or outputs, files, socket and/or net sockets). With this command, a program could wait for 3 kind of events, feed ready to read, feed ready to write and some event happened on feed. The fourth argument is a timeout in seconds, so syntax is select <feeds where wait for input>, <feeds where having to write>, <feed where something could happen>, <timeout>.

For more precision, you could use Time::Hires perl module:

perl -MTime::HiRes -pe 'BEGIN{$start=Time::HiRes::time;$sleepPerLine=1/300};select undef,undef,undef,($start + $sleepPerLine*$. - Time::HiRes::time)'

Note: $. is current input line number.

Better written as cat >catLps.pl

#!/usr/bin/perl -w

use strict;
use Time::HiRes qw|time|;

my $start=time;
my $lps=300;

$lps=shift @ARGV if @ARGV && $ARGV[0]=~/^(\d+)$/;
my $sleepPerLine=1/$lps;

print &&
    select undef,undef,undef,($start + $sleepPerLine*$. - Time::HiRes::time)
    while <>

Usage:

catLps.pl [lps] [file] [file]...

First argument lps is optional line per seconds numeric argument (default: 300)

Note: if filename is only numeric, you may have to specifiy them with path: ./3.

Like cat this could pass files given as argument and/or standard input

So we could:

TIMEFORMAT='%R' 
time seq 1 100 | ./catLps.pl 100 >/dev/null 
1.040

time seq 1 10000 | ./catLps.pl 10000 >/dev/null  
1.042

For fun:

export TIMEFORMAT='%R' ;clear ;time seq 1 $((LINES-2)) | ./catLps.pl $((LINES-2))
5
  • 2
    that looks like some serious voodoo you are doing there. that is so cool, i tried it and it works. i have no idea how you did that though. what the hell is perl select ? undef? i can look it up. amazing. Dec 31 '12 at 8:18
  • 2
    @CrisStringfellow Ok, I've added some explanation and a full script using Time::HiRes perl module for more accuracy
    – F. Hauri
    Dec 31 '12 at 9:29
  • my god. That is an awesome answer. Thank you. I tried to upvote it a second time. I am learning something by reading your wonderful explanation. Dec 31 '12 at 9:42
  • 2
    You could upvote my comments too ;-)
    – F. Hauri
    Dec 31 '12 at 9:46
  • @CrisStringfellow Answer edited: by the use of -p switch to perl command, the script was lightened!
    – F. Hauri
    Mar 31 '16 at 5:53
11

just use awk with sleep:

awk '{print $0; system("sleep .1");}' log.txt
5
  • This worked for me and for my situation was the best option rather than the script options above. Unsure why this answer is down-voted. Jan 17 '18 at 19:09
  • 2
    Unlike the perl solution it is quite readable.
    – Gunslinger
    Sep 5 '18 at 7:26
  • 1
    @Gunslinger: Syntax system(*sleep .1") will generate 10 forks / secs! This could be written perl -pe 'system "sleep .1"' log.txt: Readable too, but very expensive (not system friendly!)
    – F. Hauri
    Feb 12 '19 at 19:13
  • I too prefer this readable answer. The only thing is it will fire the shell sleep command for every line it outputs. But being a perfect readable one liner I don't care.
    – itsafire
    Jun 19 '19 at 10:46
  • If you do use this command (which works great, BTW!), have another shell open where you can kill -9 it to get it to stop... Feb 17 at 8:01
0

I'm late to the party, but I found this would be a useful learning exercise to try in python, so I'll put up what i got:

#!/usr/bin/env python3

import argparse
from time import sleep

parser = argparse.ArgumentParser(description='Echo a file slowly')
parser.add_argument('-i',
                    '--input-file',
                    type=argparse.FileType('r'),
                    default='-')
parser.add_argument('-d',
                    '--delay-in-ms',
                    type=int,
                    default='100')
args = parser.parse_args()

for line in args.input_file:
    print(line.rstrip())
    sleep(args.delay_in_ms/1000.0)

It accepts input from stdin or as an argument (-i) and by default writes one line per 1/10th of a second, but that can be changed with another argument (-d).

1
  • Thanks. I was starting to develop something similar to this idea in Python when I happened upon this Q&Ad. I note that Python also supports select docs.python.org/3/library/select.html in a similar way to Perl as used in F. Hauri's answer.
    – ybull
    Jan 28 '20 at 0:54
0

Ruby's sleep function supports floating point values, so here's an efficient and short solution based on @F.Hauri's answer:

ruby -pe 'sleep 0.00333' log.txt

or

ruby -pe 'sleep (1.0/300)' log.txt
0

I like to mention two great tools:

  1. tee
  2. cstream

tee is good for capturing stdout to file as well as stream it. i.e. we can rewrite:

$ python runexperiment.py > output.txt
$ cat output.txt

as

$ python runexperiment.py | tee output.txt

The advantage of the latter version is the output.txt is being built at the same time output is displayed on stdout. So, you don't have to wait till the end of the experment.

The other tool I mention, cstream, can be used to slow down output, e.g.

$ python runexperiment.py | cstream -t 700k | tee output.txt

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.