views:

246

answers:

3

(Context: I'm trying to monitor a long-running process from a Perl CGI script. It backs up an MSSQL database and then 7-zips it. So far, the backup part (using WITH STATS=1) outputs to a file, which I can have the browser look at, refreshing every few seconds, and it works.)

I'm trying to use 7zip's command-line utility but capture the progress bar to a file. Unfortunately, unlike SQL backups, where every time another percent is done it outputs another line, 7zip rewinds its output before outputting the new progress data, so that it looks nicer if you're just using it normally on the command-line. The reason this is unfortunate is that normal redirects using >, 1>, and 2> only create a blank file, and no output ever appears in it, except for >, which has no output until the job is done, which isn't very useful for a progress bar.

How can I capture this kind of output, either by having every change in % somehow be appended to a logfile (so I can use my existing method of logfile monitoring) just using command-line trickery (no Perl), or by using some Perl code to capture it directly after calling system()?

A: 

You can try opening a pipe to read 7zip's output.

Sinan Ünür
I tried this, and unfortunately, it behaves exactly like `>` on the command line does: no output until the program is finished.
Kev
+1  A: 

If you need to capture the output all at once then this is the code you want:

$var=`echo cmd`;

If you want to read the output line by line then you need this code:

#! perl -slw
use strict;
use threads qw[ yield async ];
use threads::shared;

my( $cmd, $file ) = @ARGV;
my $done : shared = 0;
my @lines : shared;

async {
    my $pid = open my $CMD, "$cmd |" or die "$cmd : $!";
    open my $fh, '>', $file or die "$file : $!";
    while( <$CMD> ) {
        chomp;
        print $fh $_;         ## output to the file
        push @lines, $_;    ## and push it to a shared array
    }
    $done = 1;
}->detach;

my $n = 0;
while( !$done ) {
    if( @lines ) {            ## lines to be processed
        print pop @lines;   ## process them
    }
    else {
        ## Else nothing to do but wait.
        yield;
    }
}

Another option is using Windows create process. I know Windows C/C++ create process will allow you to redirect all stdout. Perl has access to this same API call: See Win32::Process.

Rook
When I try to run this script, I get `threads: Unknown import option: async`.
Kev
(The first code snippet I didn't actually try, because I'm pretty sure it's not what I'm after: I need to continually capture the output to get the progress bar, because it keeps changing thanks to this 'rewinding' thing. A one-time statement accomplishing that seems like it would be pretty dark magic even for Perl...)
Kev
I'm not sure how I would capture the output with `Win32::Process`...
Kev
yeah I'm not sure ether. For running the 2nd snip you'll probably have to install a library with CPAN. I just know that CreateProcess can capture output, idk how to do it with perl. I'm a *nix programmer sorry.
Rook
I put `async` over to the `threads::shared` line and it stopped complaining, but the result is the same as `>`, no output until the end. Well, thanks for trying anyway, I appreciate it.
Kev
A: 

This doesn't answer how to capture output that gets rewound, but it was a useful way of going about it that I ended up using.

For restores:

  1. use 7za l to list the files in the zip file and their sizes
  2. fork 7za e using open my $command
  3. track each file as it comes out with -s $filename and compare to the listing
  4. when all output files are their full size, you're done

For backups:

  1. create a unique dir somewhere
  2. fork 7za a -w
  3. find the .tmp file in the dir
  4. track its size
  5. when the .tmp file no longer exists, you're done

For restores you get enough data to show a percentage done, but for backups you can only show the total file size so far, but you could compare with historical ratios if you're using similar data to get a guestimate. Still, it's more feedback than before (none).

Kev