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

Dear Monks,

I am writing a script, which needs to lookup IP addresses of Bonjour services. The lookups are done using mDNS command: mDNS -L "Service Name" _service._tcp domain. This command outputs to stdout few lines of information, which I am looking for and would like to store it as a string variable. Unfortunately when started from shell, this command does not quit by itself. It continues running, watching for new services that are not going to appear. How to terminate this command from perl script when it stops outputting new lines and just sits waiting for events?

Thank you fro your wise advices,

Wen

Replies are listed 'Best First'.
Re: How to read from shell command and terminate it afterwards?
by ikegami (Patriarch) on Feb 10, 2012 at 07:00 UTC
    use IO::Select qw( ); # How long (in seconds) of not getting anything is considered done. use constant TIMEOUT => 2.0; open(my $pipe, '-|', 'mDNS -L "Service Name" _service._tcp domain'); my $buf = ''; # Wait to get something. my $rv = sysread($pipe, $buf, 64*1024, length($buf)); die $! if !defined($rv); # Wait until we stop getting if ($rv) { my $sel = IO::Select->new($pipe); for (;;) { if (!$sel->can_read(TIMEOUT)) { kill KILL => $pid; last; } my $rv = sysread($pipe, $buf, 64*1024, length($buf)); die $! if !defined($rv); last if !$rv; } } close($pipe); print("Got:\n$buf\n");
      If only this would work on Windows. Life would be much simpler.

        You can use a socketpair to create a pipe out of sockets.

        use Socket qw( AF_UNIX SOCK_STREAM PF_UNSPEC ); sub _pipe { socketpair($_[0], $_[1], AF_UNIX, SOCK_STREAM, PF_UNSPEC) or return undef; shutdown($_[0], 1); # No more writing for reader shutdown($_[1], 0); # No more reading for writer return 1; }

        Use that as you would use pipe, then pass the writer end to open3.

        use IPC::Open3 qw( open3 ); open(local *CHILD_STDIN, '<', 'nul:') or die $!; _pipe(\local *FROM_CHILD, \local *CHILD_STDOUT); my $pid = open3('<&CHILD_STDIN', '>&CHILD_STDOUT', '>&STDERR', $cmd); my $pipe = \*FROM_CHILD; ... waitpid($pid, 0);
Re: How to read from shell command and terminate it afterwards?
by BrowserUk (Patriarch) on Feb 10, 2012 at 09:30 UTC

    Why other people are making such a meal of this I do not know. Here's the easy way:

    my $pid = open PIPE, "theCommand arg1 arg2 |" or die $!; my @input = map scalar <PIPE>, 1 .. 4; ## kill 3, $pid;

    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".
    In the absence of evidence, opinion is indistinguishable from prejudice.

    The start of some sanity?

      Why other people are making such a meal of this

      Because the OP didn't mention anything about 4 lines?

        True. He said:

        This command outputs to stdout [a] few lines of information, which I am looking for ...

        "a few" is generally more than two, and less than say 10. "a few" emphasizes the fact that the number, while small, is not zero but more than two.

        But mostly it suggests he knows how many.


        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".
        In the absence of evidence, opinion is indistinguishable from prejudice.

        The start of some sanity?

Re: How to read from shell command and terminate it afterwards?
by ikegami (Patriarch) on Feb 11, 2012 at 02:50 UTC

    Assuming you will always get the line you want:

    my $pid = open(my $pipe, 'mDNS -L "Service Name" _service._tcp domain +|'); my $data; while (<$pipe>) { if (/...is desired line.../) { $data = $_; last; } } kill KILL => $pid; print($data);
Re: How to read from shell command and terminate it afterwards?
by chessgui (Scribe) on Feb 10, 2012 at 06:30 UTC
    If you are sure that the process has finished its useful job and you have the pid you can kill it. I think it is best to open it as a pipe. Then you will both have the pid and can read from its STDOUT.

      Thank you, chessgui. Unfortunately I am new to perl. Could you give me a code snippet? In shell I would have done something like that:

      mDNS -L "Name" _services._tcp domain. & sleep 2 kill -s SIGINT $!

      In perl it sounds like much more complicated

      Thanks,

      Wen

        This is a rough solution. Note that this works on Windows. You have to find out how to kill a process on your particular system. Stars cmd.exe (in your system replace cmd.exe with the desired shell command), waits for its output to exceed 40 bytes then prints the output, kills cmd.exe, waits for user input, then quits.
        use IPC::Open3; use IO::Handle; use threads; use Win32::Process::Kill; $process='cmd.exe'; $pid = open3( \*CHILD_IN, \*CHILD_OUT, \*CHILD_ERR, $process ); autoflush CHILD_OUT; autoflush CHILD_ERR; threads->create(\&handle_child_out)->detach; threads->create(\&handle_child_err)->detach; unlink('out.txt'); do { open IN,'out.txt'; my $content=join('',<IN>); my $l=length($content); print "Number of bytes read: $l\n"; if($l>40) { print $content; Kill($pid); print "Ready.\n"; $x=<>; exit; } sleep(1); } while(1); sub handle_child_out { do { sysread CHILD_OUT, $content, 4092; if($content ne '') { open OUT,'>>out.txt'; print OUT $content; close OUT; } } while(1); } sub handle_child_err { do { sysread CHILD_ERR, $content, 4092; if($content ne '') { open OUT,'>>out.txt'; print OUT $content; close OUT; } } while(1); }
        Aside from the (excellent) solutions proposed here, which explain how to do this in Perl, you could also wrap the three lines you presented us, in a shell script and invoke this script in the usual way (using backticks) from Perl.

        -- 
        Ronald Fischer <ynnor@mm.st>