tags:

views:

101

answers:

4

I have a Perl script which performs some tasks, one of which is to call a system command to "tar -cvf file.tar.....".

This can often take some time so I'd like the command line to echo back a progress indicator, something like a # echoing back to screen whilst the system call is in progress.

I've been doing some digging around and stumbled across fork. Is this the best way to go? Is it possible to fork off the system command, then create a while loop which checks on the staus of the $pid returned by the fork?

I've also seen references to waitpid.... I'm guessing I need to use this also.

fork system("tar ... ")
while ( forked process is still active) {
    print #
    sleep 1
}

Am I barking up the wrong tree?

Many thanks John

+2  A: 

I would try something like this

open my $tar, "tar -cvf file.tar..... 2>&/dev/null |"
    or die "can't fork: $!";
my $i = 0;
while (<$tar>) {
    if( i++ % 1000 == 0 ) print;
} 
close $tar or die "tar error: $! $?";
Peter G.
Please use local lexicals for your file/process handles, not globals.
Ether
My perl is a little rusty, I didn't know this is possible for filehandles. Thanks, I'll try that next time.
Peter G.
+5  A: 

Perl has a nice construction for this, called "pipe opens." You can read more about it by typing perldoc -f open at a shell prompt.

# Note the use of a list for passing the command. This avoids
# having to worry about shell quoting and related errors.
open(my $tar, '-|', 'tar', 'zxvf', 'test.tar.gz', '-C', 'wherever') or die ...;

Here's a snippet showing an example:

  open(my $tar, '-|', 'tar', ...) or die "Could not run tar ... - $!";
  while (<$tar>) {
       print ".";
  }
  print "\n";
  close($tar);

Replace the print "." with something that prints a hash mark every 10 to 100 lines or so to get a nice gaugebar.

Johan
Hi Johan...that is exactly what i'm after! Many thanks for your reply and example. I always find it much easier when seeing example code.RegardsJohn
john
one question....why the comma on the print line? What does that do?
john
The comma is not needed, I removed it from the example ;-)
Johan
:-) - Thanks Johan. One thing....does this method only work on commands which write something to STDOUT? It seems to work perfectly when using tar zcvf, but there's another command i'm using which does not echo anything back to STDOUT.....when I try and open a pipe to this command, i don't see the print "." :(
john
@john yes, it only works if the command writes something. If the child process doesn't write anything then you need to use a different construction, like `fork`/`exec`/nonblocking `waitpid`/`sleep`.@Johan your code sample would be better with a `$|++` so that the dots are printed immediately. :)
hobbs
+4  A: 

An example that doesn't depend on the child process writing any kind of output, and just prints a dot about once a second as long as it's running:

use POSIX qw(:sys_wait_h);
$|++;

defined(my $pid = fork) or die "Couldn't fork: $!";

if (!$pid) { # Child
  exec('long_running_command', @args) 
    or die "Couldn't exec: $!";
} else { # Parent
  while (! waitpid($pid, WNOHANG)) {
    print ".";
    sleep 1;
  }
  print "\n";
}

Although it could probably stand to have more error-checking, and there might actually be something better already on CPAN. Proc::Background seems promising for abstracting this kind of job away but I'm not sure how reliable it is.

hobbs
Hi and thanks for your help on this. I will give this a try also and see which method best suits. I liked your $|++ tip above. I've been doing a select(STDOUT) and then $|=1
john
+1  A: 

For showing progress during a long-running task, you will find Term::ProgressBar useful -- it does the "printing of # across the screen" functionality that you describe.

Ether