Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling

Progress Bar in Perl script

by slayedbylucifer (Scribe)
on Jul 08, 2012 at 09:48 UTC ( #980562=perlquestion: print w/ replies, xml ) Need Help??
slayedbylucifer has asked for the wisdom of the Perl Monks concerning the following question:


I have written a perl script which will write some output to a file. However, it takes about 2-3 minutes for the script to complete due to the amount of data involved. So, I wanted to show some progress bar so that user would know that something is going on and that the script is not stuck.

I checked on the CPAN and find there are lots of modules for this job. However, all of them need a predefined MAX counter as an input. Now, in my case I cannot define the MAX counter because it is not constant. The number of items the script will be working on with is not constant.

So is there a way to display progress bar in this case wherein I do not have to provide a MAX counter.


Comment on Progress Bar in Perl script
Re: Progress Bar in Perl script
by Athanasius (Monsignor) on Jul 08, 2012 at 10:06 UTC

    Since you don’t have a MAX counter, the progress you want to show is not a proportion of the total job, but just something that shows that ‘progress’ is being made. So, why would you need a CPAN module? Presumably, your output occurs in a loop, so:

    my $progress = 100; # adjust as needed my $count = 0; while (...) { ... show_progress() if (++$count % $progress == 0); } progress_complete(); # continue main code sub show_progress { print '*'; } sub progress_completed { print " completed\n"; }

    You don’t say whether the output is via console or GUI. The above is written for the console, but should be easily adaptable for a GUI if required.


    Athanasius <°(((><contra mundum

      Yes. that is correct. I just wanted to show something like a "spinning" cursor progress which will tell the user that script is doing its job. I do not want to show how much percentage is completed. This is meant for Linux console on a RHEL 6.2 box.

      Thanks Athanasius for your time.

        So you can use the Term::Spinner module. It can be easily done manually, too:
        $|=1; #autoflush sub spin { my @spinners = qw{/ - \ |}; my $index = $_[0]; print $spinners[$index]."\033[1D"; # man console_codes, ECMA-48 CSI se +quences, "CUB" return $index == 3 ? 0 : $index++; } my $j=0; for (something) { do_something; $j=&spin($j); }
        Sorry if my advice was wrong.
Re: Progress Bar in Perl script
by zentara (Archbishop) on Jul 08, 2012 at 10:54 UTC
    A spinner in a thread is your best bet.
    #!/usr/bin/perl use strict; use threads qw[ yield ]; use threads::shared; # by chanio of perlmonks # This alternative should print a line only if it succeeds # in what is doing as job. That way, there is a real # significance of the spinning wheel (sort of:). When the # job is not succeeding, the wheel should stay calm (panic!). my $ready : shared = 0; my $isOk : shared = 0; async { local $| = 1; while ( !$ready ) { do { select undef, undef, undef, 0.2; printf "\r ($_)" if ($isOk); } for qw[ / - \ | * ]; } print "\rReady"; $ready = 0; } ->detach; # do your work here for ( 1 .. 10 ) { ## Busy, busy, busy $isOk = 1; sleep 1; $isOk = 0; } $isOk = 0; $ready = 1; yield while $ready;

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh
      Hi, thanks for your time. But honestly, i did not understand the thread stuff. May be this is more intended for Developers.
Re: Progress Bar in Perl script
by frozenwithjoy (Curate) on Jul 08, 2012 at 17:42 UTC

    Your question reminded me of a quick little progress bar I wrote during Nick Patch's Unicode talk at YAPC::NA 2012. I had never messed with unicode before, but he inspired me with the U+1F4A9 on one of his slides. This won't help you the way it is written, but maybe it can inspire you!

    #!/usr/bin/env perl use strict; use warnings; use utf8; $| = 1; binmode STDOUT, ":utf8"; my $camel = "\N{U+1F42A}"; my $poop = "\N{U+1F4A9}"; print $camel; for (1..60) { sleep 1; print " $poop"; } print "\n";

    Also, the other inspiration for this was Perl Object Oriented Programming

      I saw something similar written for BASH on internet. Thank you very much for your suggestion. I will definitely work on it.
Re: Progress Bar in Perl script
by kcott (Abbot) on Jul 08, 2012 at 21:30 UTC

    You could take a look at Smart::Comments - this might do what you want (it doesn't require a MAX counter). Here's a quick commandline test you can run:

    $ perl -Mstrict -Mwarnings -e ' use Smart::Comments; for (1 .. 500) { ### Running... done qx{ls -l > /dev/null 2>&1}; } '

    Here's my take on a Roll-Your-Own spinner. This has 6 tests: the first 3 are expected to fail; the next three have fast, medium and slow spin rates. The spinner becomes an asterisk if the process completes successfully.

    #!/usr/bin/env perl use strict; use warnings; my $CMD = q{ls -l > /dev/null}; for ([], [q{}], [q{dummy}], [$CMD, 10], [$CMD], [$CMD, 50]) { eval { run_with_progress(@$_); }; warn $@ if $@; } sub run_with_progress { print q{-} x 60, qq{\nStarting ...\n}; my ($cmd, $skip) = @_; die q{Nothing to run!} unless $cmd; $skip ||= 25; # Arbitrary my @sails = map { "\b$_" } qw{- \ | /}; my $sail = 0; { local $| = 1; print q{Running: }; for (0 .. 1000) { system $cmd and die qq{'$cmd' failed! $? : $!}; print $sails[$sail++ % @sails] if not $_ % $skip; } } print qq{\b*\nFinished.\n}; }


    $ ------------------------------------------------------------ Starting ... Nothing to run! at ./ line 20. ------------------------------------------------------------ Starting ... Nothing to run! at ./ line 20. ------------------------------------------------------------ Starting ... Running: Can't exec "dummy": No such file or directory at ./pm_text_s line 32. 'dummy' failed! -1 : No such file or directory at ./pm_text_spinner_ry line 32. ------------------------------------------------------------ Starting ... Running: * Finished. ------------------------------------------------------------ Starting ... Running: * Finished. ------------------------------------------------------------ Starting ... Running: * Finished.

    Update: I revisited this and reworked run_with_progress() to use Smart::Comments but with a spinner instead of the normal progress bar. The messages are the same as shown above and the spin rates vary in the same manner; the formatting is different.

    sub run_with_progress { use Smart::Comments; ### Starting ... my ($cmd, $skip) = @_; die q{Nothing to run!} unless $cmd; $skip ||= 25; # Arbitrary my @sails = map { ($_) x $skip } qw{- \ | /}; my $sail = 0; for (0 .. 1000) { ### Running: $sails[$sail++ % @sails] system $cmd and die qq{'$cmd' failed! $? : $!}; } ### Finished. no Smart::Comments; return; }

    This code is a lot cleaner; however, I'm not a big fan of the output.

    Update 2: I also reworked run_with_progress() to use Term::Spinner. Messages, spin rates, etc. work the same as the other two; the output is almost identical to the Roll-Your-Own version. This would be my favourite ... at least until I find something better. :-)

    sub run_with_progress { print q{-} x 60, qq{\nStarting ...\n}; my ($cmd, $skip) = @_; die q{Nothing to run!} unless $cmd; $skip ||= 25; # Arbitrary use Term::Spinner; my $spinner = Term::Spinner::->new(); print q{Running: }; for (0 .. 1000) { system $cmd and die qq{'$cmd' failed! $? : $!}; $spinner->advance() if not $_ % $skip; } $spinner->finish(); print qq{\nFinished.\n}; undef $spinner; return; }

    -- Ken

      This is a great explanation. thank you for your time. i will work on the 3rd option that you mentioned.
      I've been away from Perl for too long... I hope I'm not misunderstanding some fundamental principal here...

      When I run this code from the command line, what I don't understand is how the "spinner" is printing over itself to the screen.
      I understand that @sail is being interated through by incrementing $sail, but what part of this code is making the contents of @sail print on top of each other, i.e. making the "spinner" effect?

      my @sails = map { "\b$_" } qw{- \ | /}; my $sail = 0; { local $| = 1; print q{Running: }; for (0 .. 1000) { system $cmd and die qq{'$cmd' failed! $? : $!}; print $sails[$sail++ % @sails] if not $_ % $skip; } }

      Any help here? What am I overlooking?

        Each of the sails is created by prepending a "\b" to the spinner character:

        my @sails = map { "\b$_" } qw{- \ | /};

        "\b" is a backspace character; so, when each sail is printed, it deletes the previous one. In the initial state, "Running:" is followed by two spaces (print q{Running:  };); the second space is overwritten by the first sail.

        "\b", and other special characters, are listed in perlop: Quote and Quote-like Operators.

        -- Ken

Re: Progress Bar in Perl script
by Jim (Curate) on Jul 10, 2012 at 00:16 UTC

    I usually do something simple like this:

    printf {*STDERR} "%d\r", $INPUT_LINE_NUMBER;


    printf {*STDERR} "%d\r", ++$tally_of_something;

    This has the advantage of not only showing that the script is actively doing something, but of also giving the user an idea of the amount of progress even when the percentage of progress cannot be computed.


Re: Progress Bar in Perl script
by Rhandom (Curate) on Jul 10, 2012 at 14:36 UTC
    If you are on a system that supports zenity - it is yet another option. It uses a real progress bar which may not be what you want (will only work on your local machine). Here is a very long one liner that makes the use of most zenity progress options (you could shorten it to zenity --progress --pulsate):

    perl -e 'open my $fh, "|-", q{zenity --title=Working --width=400 --pro +gress --text="Still going..." --percentage=50 --no-cancel --auto-kill + --auto-close}; select $fh; $|=1; select(undef,undef,undef,.1),print +$fh "$_\n#Now on $_...\n" for 1..100'

    Here is the code broken out into more of a program.

    use IO::Handle; # for older perls use Time::HiRes qw(usleep); my $pid = open my $fh, "|-", q{zenity --title=Working --width=400 --pr +ogress --percentage=50 --no-cancel --auto-kill --auto-close} or die "Could not open zenity: $!"; $fh->autoflush(1); for my $i (1 .. 100) { usleep 100_000; # microseconds print $fh "$i\n"; # move the bar print $fh "#Now on $i...\n"; # update the text print "We've done $i\n"; if ($i >= 50) { #kill 2 $pid; # not as portable print $fh "100\n"; # more portable with --auto-kill last; } }

    my @a=qw(random brilliant braindead); print $a[rand(@a)];

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://980562]
Approved by Corion
Front-paged by toolic
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (11)
As of 2014-09-18 10:54 GMT
Find Nodes?
    Voting Booth?

    How do you remember the number of days in each month?

    Results (111 votes), past polls