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

I have a bit of code that looks like this:
sub nslookup { my ($input, $query_type) = @_; # Fork a child process to exec nslookup, # and capture its output if (open(NSLOOKUP, "-|")) { # Parent process. Read output from child. my @output = <NSLOOKUP>; splice(@output, 0, 3); return @output; } else { # Child process. exec() nslookup exec("/usr/sbin/nslookup", "-q=$query_type", $input, $nameserver); } }
Is there an easy way I can capture STDERR from the child process as well as STDOUT? As I see it, these are my options:

1) Rewrite with backticks:

@output = `/usr/sbin/nslookup -q=$query_type $input $nameserver 2>&1`;
I don't like this because it means I have to send user input into backticks. My whole point in open()ing "-|" was to feed the args into exec() as an array. I'm already scrubbing the input anyway, but better safe than sorry, eh?

2) Rewrite using open3(). open3() looks evil.

3) Rewrite using Net::DNS instead of executing nslookup. This is the cleanest, most portable option, but it will take more time than this script is worth because I'll have to completely rewrite the output handling.

-Matt

Replies are listed 'Best First'.
Re: Capturing STDERR with -
by MZSanford (Curate) on Dec 05, 2001 at 20:41 UTC
    hmm, in the child is think you could do something similar to (untested code ahead):
    open(STDERR,">&STDOUT");

    This will re-open STDERR to be joined with STDOUT, i think.
    $ perl -e 'do() || ! do() ;' Undefined subroutine &main::try
Re: Capturing STDERR with -
by traveler (Parson) on Dec 05, 2001 at 20:52 UTC
    Since you appear to be on UNIX or Linux, you should be able to do the following:
    exec("/bin/sh", "-c", "/usr/sbin/nslookup -q=$query_type $input $names +erver 2>&1");
    Of course, if your shell is not in /bin, use the correct path.

    HTH, --traveler

Re: Capturing STDERR with -
by Fastolfe (Vicar) on Dec 06, 2001 at 01:14 UTC
    I recommend the open(STDERR, ">&STDOUT") solution, unless you really need both channels to be separate, where your script can tell the difference between STDERR output and STDOUT output. I also suggest you add something to your exec line:
    exec("/usr/sbin/nslookup", ...) or die "Could not exec: $!"

    As it is now, if your exec failed, execution would continue through your script. If this code were in a loop, you would have written yourself a pretty effective fork bomb.

      open(STDERR, ">&STDOUT") was actually the first thing I tried, but I'm afraid it doesn't work. I think the issue is that I'm exec()ing a new process. Open file handles for the perl script go away when the process is replaced.

      -Matt

        Try it again. I modified your 'exec' block to look like this:

        open(STDERR, ">&STDOUT") if $redirect_stderr; exec("/usr/bin/nslookup", "-q=$query_type", $input, $nameserver) or die "exec: $!";

        I also took out the 'splice' and ran your function like this:

        print "Without:\n"; print "| $_\n" foreach nslookup('test', 'A'); print "\n"; $redirect_stderr++; print "With:\n"; print "| $_\n" foreach nslookup('test', 'A');

        My output was this:

        Without: Note: nslookup is deprecated and may be removed from future releases. Consider using the `dig' or `host' programs instead. Run nslookup wit +h the `-sil[ent]' option to prevent this message from appearing. | *** Invalid option: q=A | Server: ns.intranet | Address: 10.0.0.1#53 | | ** server can't find test: NXDOMAIN With: | Note: nslookup is deprecated and may be removed from future release +s. | Consider using the `dig' or `host' programs instead. Run nslookup w +ith | the `-sil[ent]' option to prevent this message from appearing. | *** Invalid option: q=A | Server: ns.intranet | Address: 10.0.0.1#53 | | ** server can't find test: NXDOMAIN

        The 'deprecated' warning is sent out via STDERR, which is being captured in the second run. So it seems to be working perfectly for me.

        Note that even though you're doing an exec, file descriptors 0, 1 and 2 (STDIN, STDOUT and STDERR) are inherited by the child process. (This is configurable with $^F; see perlvar.) Since all you're doing here is 'dup'ing 2 to be the same as 1, all 3 get passed as-is and work as you expect.

        On a lesser note, if it is this warning you're trying to omit, is there any reason you're still coding your script to use 'nslookup' instead of, as it recommends, 'dig' or 'host'? The 'dig' tool is pretty easily parseable and I think intended for applications like this moreso than nslookup was. Just a thought..

Re: Capturing STDERR with -
by Caillte (Friar) on Dec 05, 2001 at 20:43 UTC

    I haven't tested it but, a quick glance through CPAN came up with IPC::ChildSafe. This allows the opening of another shell, controlled by the caller. Output from the new shell can then be read back into the caller.

    $japh->{'Caillte'} = $me;

Re: Capturing STDERR with -
by IlyaM (Parson) on Dec 07, 2001 at 00:10 UTC
    I suggest you to use Net::DNS. It is not very hard since there is no complex output handling. It has quite simple API and it doesn't require you to do any parsing.

    P.S. Another reason to avoid nslookup is that it is deprecated.

    bash-2.05a$ nslookup Note: nslookup is deprecated and may be removed from future releases. Consider using the `dig' or `host' programs instead. Run nslookup wit +h the `-sil[ent]' option to prevent this message from appearing.

    --
    Ilya Martynov (http://martynov.org/)

      At a guess, nslookup is probably only "deprecated" on a couple of linux distributions.
      $ uname -imprs SunOS 5.8 sun4u sparc SUNW,UltraAX-i2 $ host zsh: command not found: host $ dig zsh: command not found: dig $ nslookup Default Server: bastion.cox.com Address: 206.98.131.2 >

      -Matt

        Actually it have been deprecated by bind maintainers. Some linux distributions do have latest bind but thats all.

        --
        Ilya Martynov (http://martynov.org/)