Cat file to terminal at particular speed of lines per second

14

7

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.

Cris Stringfellow

Posted 2012-12-31T07:31:26.640

Reputation: 709

7Try 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. – David Schwartz – 2012-12-31T07:54:32.007

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. – Cris Stringfellow – 2012-12-31T08:03:33.413

Answers

17

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

F. Hauri

Posted 2012-12-31T07:31:26.640

Reputation: 1 394

@CrisStringfellow Answer edited: by the use of -p switch to perl command, the script was lightened! – F. Hauri – 2016-03-31T05:53:09.683

2that 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. – Cris Stringfellow – 2012-12-31T08:18:33.690

2@CrisStringfellow Ok, I've added some explanation and a full script using Time::HiRes perl module for more accuracy – F. Hauri – 2012-12-31T09:29:17.130

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. – Cris Stringfellow – 2012-12-31T09:42:01.173

2You could upvote my comments too ;-) – F. Hauri – 2012-12-31T09:46:15.103

11

just use awk with sleep:

awk '{print $0; system("sleep .1");}' log.txt

DMas

Posted 2012-12-31T07:31:26.640

Reputation: 111

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. – Citizen Kepler – 2018-01-17T19:09:16.963

2Unlike the perl solution it is quite readable. – Gunslinger – 2018-09-05T07:26:30.473

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 – 2019-02-12T19:13:01.433

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 – 2019-06-19T10:46:07.633

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

Grezzo

Posted 2012-12-31T07:31:26.640

Reputation: 752

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 https://docs.python.org/3/library/select.html in a similar way to Perl as used in F. Hauri's answer.

– ybull – 2020-01-28T00:54:58.837