Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hello Wise Monks,

I have a C program that constantly returned the % of the job completed to my Perl script so to execute this program I have these lines:

use IO::Handle; $| = 1; $data1 = "./myC.exe 10 10 1 abc"; open PS, "$data1 |"; PS->autoflush(1); while ($line = readline(*PS)) { print $line; print "\n"; }

I have noticed that the results are only returned to my Perl script after my C program are completed and not while it is running. The C program usually takes about 10 hours to complete and every half an hour or so it prints to STDOUT how many percent of the job has been completed so that the Perl script can take it and push it to the web page. I have tried

use IO::Handle; $| = 1; $data1 = "./myC.exe 10 10 1 abc"; open PS, "$data1 |"; PS->autoflush(1); while (<PS>) { print; print "\n"; }

but no luck. It appeared that the result are buffered or stucked somewhere while the C program is running and only get returned to my Perl script after the C program exited. Any help would be appreciated.

Thanks in advance.

Code tags added by GrandFather

Replies are listed 'Best First'.
Re: Pipe Problem
by graff (Chancellor) on Jul 23, 2007 at 02:36 UTC
    As mentioned in one of the replies above, if the C program does not flush its stdout, its output will be buffered until the pipe connection has accumulated (I think) 8 KB or so of data (or else has finished its work), before the downstream process will get to see any data.

    (When output goes directly to the terminal window, stdout does not get buffered -- the buffering applies when the output is going to a pipe or file. You could test this out by running your C program at the command line like this:

    ./myC.exe 10 10 1 abc > test.out
    Then use another window to watch the size of the "test.out" file. If the C code is not flushing stdout at each printf statement, the file won't grow until the program has written 8K of data, or until it finishes, whichever comes first.)

    And apart from the output buffering in the C program, if the strings being printed to stdout do not end in a line-feed, the perl script might still not print anything until it detects and EOF on the pipe. The default input record separator in perl is "\n" (with windows CRLF handled as needed in the default windows/perl setup) -- (update:) perl will keep reading into $_ on the first iteration of  while (<>) until it gets a line-feed or EOF.

    When people do those kinds of progress-report string outputs, they often terminate each print with "\r", so that each iteration overwrites the previous string. If your C program is doing that, have your perl script use "\r" as the input record separator (the perl-internal global variable "$/") -- update: in other words, do this:  $/ = "\r";

    And sometimes, the C code has been written so that the status output has no termination at all -- each iteration simply appends onto one line. If your C program is set up that way and you want to keep that "feature", you'll want to modify the C code to at least use some distinctive character (or string) at the end of each report iteration, so the perl script can use that character (or string) as the input record separator.

      Thank you all for your valuable inputs. I will ask the person who wrote the C program tomorrow to verify that his C program flushes its outputs after each print statement. I will report back the result on this forum tomorrow. Thanks again.
Re: Pipe Problem
by BrowserUk (Patriarch) on Jul 22, 2007 at 21:35 UTC

    Not sure if this will help or not, but I had a similar problem a few years ago. On that occasion the progress message included a reference to a filename supplied via the command line. I discovered that by supplying a filename with a whole bunch of trailing spaces, it didn't affect the programs ability to open the file, but when it displayed the message it incorporated the trailing spaces. By adding enough spaces, it cause the pipe buffer to fill and so be output.

    It doesn't look from your example that any of the parameters are paths, but if any of them are displayed as part of the progress message, you might try something like:

    $data1 = sprintf "./myC.exe 10 10 1 \"abc%s\"", ' ' x 4096;

    or even, if the executable name is part of the message:

    $data1 = sprintf "%smyC.exe 10 10 1 abc", './' x 2048;

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Thank you for your response. I can modify the C program to output the trailing spaces. Is there a way I can find out how big my system's buffer is so I can output the necessary trailing spaces?

        Sorry. I assumed that you did not have the source to the C program. If you can modify the source, you should be able to call the C function flush() (or fflush()) on stdout/stderr after printing each progress message.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Pipe Problem
by zentara (Cardinal) on Jul 23, 2007 at 11:34 UTC
    Another option may be to setup a pseudo(fake) tty with IO::Pty, but I don't know if it works on MSWindows. Check how the following script runs your C.exe.
    #!/usr/bin/perl -w # Description: Fool a process into # thinking that STDOUT is a terminal, when in fact # it may be a file or a pipe. This can be useful # with programs like ps and w on linux... which # will trunc their output to the width of the # terminal, and, if they cannot detect the terminal # width, use a default 80 columns. Wouldn't it be # nice to say "ps -aux | grep etcshadow", and get # output that looks like when you just say "ps # -aux"? Well, that's the idea. #try ./pseudotty "xterm -e top" #or ./pseudotty top use warnings; use strict; use IO::Pty; die "usage: ptyexec command [args]\n" unless @ARGV; my $pty = IO::Pty->new; my $slave = $pty->slave; open TTY,"/dev/tty" or die "not connected to a terminal\n"; $pty->clone_winsize_from(\*TTY); close TTY; my $pid = fork(); die "bad fork: $!\n" unless defined $pid; if (!$pid) { # $slave->close(); open STDOUT,">&=".$pty->fileno() or die $!; exec @ARGV; }else{ $pty->close(); while (defined (my $line = <$slave>)) { print $line; } } #cleanup pty for next run $pty->close();

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
      It's working. You guys are great. After we put in the fflush(stdout) in the C program, everything works like a charm, Thanks again.