Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

[SOLVED] Capturing errors from 3-arg pipe open in ActivePerl 5.020

by ateague (Monk)
on Nov 16, 2015 at 16:51 UTC ( [id://1147816]=perlquestion: print w/replies, xml ) Need Help??

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

UPDATE:
Calling eof on the file handle after the pipe open catches the error.

open (ARTICLE, "-|", "caesar <article"); eof ARTICLE and die "Can't start caesar:\n$!\n$^E";

Thank you BrowserUk for your help!




Good morning!

I have a question regarding ActivePerl 5.020 on Windows: How do I catch errors with a 3-arg pipe open?

Perldoc offers the following example in the open documentation under "General Examples"

open(ARTICLE, "-|", "caesar <article") or die "Can't start caesar: $!"; # N.B. this is never reached

I would expect this to fail with the "Can't start caesar" error since I do not have a "caesar" program in $PATH, but the die portion is never reached.

Am I doing something wrong? Do things work differently when running under Windows?

Thank you for your time

edit:
Just for clarification, I am wondering why the example provided from Perldoc does not work as advertised

Replies are listed 'Best First'.
Re: Capturing errors from 3-arg pipe open in ActivePerl 5.020
by Lotus1 (Vicar) on Nov 16, 2015 at 17:17 UTC

    From the same document a few paragraphs up from the General Examples section:

    Open returns nonzero on success, the undefined value otherwise. If the open involved a pipe, the return value happens to be the pid of the subprocess.

    Even if the command isn't on your system the pipe is opening a subprocess and returning the pid. Here's what I see on Windows 8.

    use warnings; use strict; print "here it goes...\n"; my $return_val = open(my $article, "-|", "caesar <article"); # or die "Can't start caesar: $!"; print "tried it and got [$return_val]\n";
    c:\usr\pm>openprog.pl > openprog.txt 2>&1 here it goes... The system cannot find the file specified. tried it and got [14048]
    Update
    use warnings; use strict; use Win32::Process::List; $!=0; print "here it goes...<$!>\n"; my $return_val = open(my $article, "-|", "caesar <article"); # or die "Can't start caesar: $!"; print "tried it and got [$return_val] <$!>\n"; print "="x75,"\n"; my %list = Win32::Process::List->new()->GetProcesses(); #return +s the hashes with PID and process name foreach my $key ( keys %list ) { # $list{$key} is now the process name and $key is the PID print sprintf("%30s has PID %15s", $list{$key}, $key) . "\n" if $k +ey == $return_val; } print "="x75,"\n";
    here it goes...<> The system cannot find the file specified. tried it and got [8664] <Inappropriate I/O control operation> ====================================================================== +===== cmd.exe has PID 8664 ====================================================================== +=====

      Sorry, I feel a bit dense here, but what do I do with the PID once I have it? I get a PID regardless of whether or not the command succeeded.

      pipe.pl

      #!/usr/bin/perl use 5.018; use strict; use warnings; my $pid = open (my $ARTICLE, "-|", "caesar") or die "Can't start caesa +r: $!\n$^E"; my $read = <$ARTICLE>; say "[$read][$pid]";
      Results:
      perl pipe.pl 'caesar' is not recognized as an internal or external command, operable program or batch file. Use of uninitialized value $read in concatenation (.) or string at pip +e.pl line 9. [][1236]

        You asked in the OP what is different about your command and why isn't the 'or die' clause working in your open command. I provided documentation that shows if you open a pipe it returns the pid instead of just non-zero or zero like with opening a file. That is the difference and the answer to your question.

        I wasn't telling you to do anything with the pid, only that it is the return value and the reason why your code didn't work as expected.

        I don't think this example should be included in the documentation for open since it doesn't work as expected for pipes.

Re: Capturing errors from 3-arg pipe open in ActivePerl 5.020
by BrowserUk (Patriarch) on Nov 16, 2015 at 18:01 UTC

    The problem is that perl is starting cmd.exe that is then asked to run the non-existent command. So the pid is for the shell instance. (Watch the taskmanager to see this is happen.)

    To avoid that problem you can try passing the fully qualified pathname of the command you want to run.

    Without the command being fully qualified, 'caeser' might be caeser.exe or caeser.bat or caeser.cmd or caeser.pl or caeser.vb etc. And it might be in the current directory, or somewhere in the path or.... Rather than Perl having to emulate all of the possibilities, unqualified commands are passed to the shell to do what it does.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    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". I knew I was on the right track :)
    In the absence of evidence, opinion is indistinguishable from prejudice.

      Thank you. That certainly makes sense. I feel though I should probably back up and clarify the root problem though.

      I am working with a collection of PDF files and am using the pdftohtml.exe program to convert the PDF into an XML stream in order to extract text of interest with XML::Twig:

      open (my $XML, "-|", "e:\\path\\to\\pdftohtml.exe -xml -zoom 1.4 -stdo +ut $PDF_FILE") or die "pdftohtml failed:\n$!\n$^E"; my $t = XML::Twig->new( twig_handlers => { '/pdf2html/pagetext[(@top >= 180 and @top <= 190) and (@left > += 100 and @left <= 111)]' => \&RouteTo, '/pdf2html/pagetext[(@top >= 215 and @top <= 225) and (@left > += 260 and @left <= 270)]' => \&InvoiceSort, '/pdf2html/page' => sub { $_[0]->purge; 1; }, # free memory af +ter every page }, comments => 'drop', # remove any comments empty_tags => 'normal',# empty tags = <tag/> ); $t->parse($XML); close $XML;

      The problem is that if I fat-finger the open command (e.g. type "-zom" instead "-zoom" in the command arguments), or if "$PDF_FILE" could not be found, the program merrily continues on its way, unaware that $XML is undefined. I've been working around this by wrapping the "$t->parse" in an eval block to catch this, but I was wondering if there was a better way.

        1. The problem is that if I fat-finger the open command (e.g. type "-zom" instead "-zoom" in the command arguments)

          You ought to detect that kind of error the first time you test your script; so correct the typo.

        2. or if "$PDF_FILE" could not be found, the program merrily continues on its way, unaware that $XML is undefined.

          This kind of depends on what the executable does in that situation. I'll assume it does the sensible thing of outputting an error message then exits with a non zero exit code.

          Normally, if you were reading the pipe yourself, the first time you attempted to read it would get a end of file (with a pipe abandoned status) and you could then call waitpid on the pid returned by the open, and check $? to obtain the exit code and status.

          As you are passing the filehandle into a module, the simplest check would be to call eof on the filehandle before you give it to XML::Twig; and if there's nothing to read, don't pass it on; just waitpid and check $?

        It can get more complicate if the executable is one of those that tries to be 'helpful' and hangs around rather than just exiting on error; but let's assume it's not :)


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        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". I knew I was on the right track :)
        In the absence of evidence, opinion is indistinguishable from prejudice.
        ...eval block to catch this, but I was wondering if there was a better way.
        From the XML::Twig docs:

        safe_parse ( SOURCE [, OPT => OPT_VALUE ...])

        This method is similar to parse except that it wraps the parsing in an eval block. It returns the twig on success and 0 on failure (the twig object also contains the parsed twig). $@ contains the error message on failure.

        Note that the parsing still stops as soon as an error is detected, there is no way to keep going after an error.

                “The sources of quotes found on the internet are not always reliable.” — Abraham Lincoln.3; cf.

        If I'm understanding correctly, you're basically wanting to call another program from your Perl code and capture the STDOUT and STDERR of that program so that your code can determine if the program ran successfully or encountered errors. Is that correct?

        If the description above is correct, then my approach would be to leverage Capture::Tiny instead of using the piped open construct.

Re: Capturing errors from 3-arg pipe open in ActivePerl 5.020
by 1nickt (Canon) on Nov 16, 2015 at 17:07 UTC

    Can't solve your problem, but for what it's worth it does fail properly on my OS X system. Interestingly though, if I call it like you did with an argument to the non-existent program, *that* error is raised first:

    [502]&#9658; perl -E' open ( FOO, "-|", "caesar" ) or die $!; ' No such file or directory at -e line 2. [503]&#9658; perl -E' open ( FOO, "-|", "caesar <article" ) or die $!; ' sh: article: No such file or directory

    The way forward always starts with a minimal test.

      I got similar results: The shell throws an error, but it never makes it to Perl

      Pipe.pl
      #!/usr/bin/perl use 5.018; use strict; use warnings; open (my $ARTICLE, "-|", "caesar") or die "Can't start caesar: $!\n$^E +"; my $read = <$ARTICLE>; say "[$read]";
      Result:
      perl pipe.pl 'caesar' is not recognized as an internal or external command, operable program or batch file. Use of uninitialized value $read in concatenation (.) or string at pip +e.pl line 9. []
Re: Capturing errors from 3-arg pipe open in ActivePerl 5.020
by ikegami (Patriarch) on Nov 16, 2015 at 20:49 UTC
    As with system and exec,
    open (my $ARTICLE, "-|", $shell_command)
    is short for
    open (my $ARTICLE, "-|", "cmd", "/x", "/c", $shell_command)

    open successfully executed cmd as requested. No error occurred, so it returned the pid of the child.

    Presumably, cmd will return an error for being unable to execute ceasar. Of this you will be notified when you close the handle. It will return false with a true value for $? >> 8.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (7)
As of 2024-04-19 09:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found