Perl: Piping child stdout to parent process - perl

In Perl, I can open a child process and pipe its output to the calling Perl script, like this:
open(my $cmd, '-|', 'ls') or die $!;
while (<$cmd>) {
print $_;
}
This prints the files in my working folder, e.g.:
> foo.txt
> bar.txt
> ...
But I would like to do the same thing for a child process that remains open, e.g. to pipe tcpdump's stdout to Perl, I attempt something similar:
open(my $cmd, '-|', 'tcpdump') or die $!;
while (<$cmd>) {
print $_;
}
... but other than the tcpdump startup text, this doesn't mete out any http logs. It just seems to hang. What gives?

It was buffering issues. I needed to add the -U flag to tcpdump. This causes packets to be written as soon as they're received.

Related

IPC::Open2 output to already open file handle as per doc example

The documented example in perldoc IPC::Open2 (read from parent STDIN and write to already open handle) is a simplified version of what I'm trying to achieve. Namely, parent writes a preamble to a output file, then a subprocess writes its output directly to the same file.
I've made a simple child script which reads input lines and prints to STDERR and STDOUT. The STDOUT being the the 'already open handle' from the parent.
#!/usr/bin/env perl
##parent.pl
use IPC::Open2;
# read from parent STDIN and write to already open handle
open my $file, '>', 'outfile.txt' or die "open failed: $!";
my $pid = open2($file, "<&STDIN", "./child.pl");
# reap zombie and retrieve exit status
waitpid( $pid, 0 );
my $child_exit_status = $? >> 8;
#!/usr/bin/env perl
##child.pl
while(<STDIN>){
print STDOUT "STDOUT: ",$_;
print STDERR "STDERR: ", $_;
}
print STDERR "END OF CHILD\n";
An example run of parent.pl:
Hello
^D
STDERR: Hello
STDERR: END OF CHILD
However, I don't see the expected "STDOUT: Hello" in the output file 'outfile.txt'
Is there some additional setup I've missed to get this example to work?
open my $file, '>', 'outfile.txt' or die "open failed: $!";
my $pid = open2($file, "<&STDIN", "./child.pl");
This will create a new pipe, and overwrite the $file variable with a handle refering to the read end of the pipe, closing the old file handle in the process ;-)
In order to pass an existing file handle to open2 or open3, you want to use the >&FILEHANDLE format, but I wasn't able to figure out any way to do that when FILEHANDLE is a local variable, as your my $file.
But the undocumented >&NUM or >&=NUM forms (where NUM is a file descriptor number) just work:
open my $file, '>', 'outfile.txt' or die "open failed: $!";
my $pid = open2('>&'.fileno($file), '<&STDIN', './child.pl');
Example:
$ perl -MIPC::Open2 -e '
open my $f, ">foo";
open2(">&".fileno($f), "<&STDIN", "echo bar")
'; cat foo
bar

Why is piping my script's output into the shell's dir command not working as I expected?

I am piping the directory command output to file handle, followed by the print to the file handle. I wish to append some text to the dir/ls output.
open (FH, "| dir") or die "$OS_ERROR";
print FH ("sometext") or die "$OS_ERROR";
while (<FH>){
print;
}
When I execute the Perl script, I see the directory contents, but I do not see the text printed using the print statement, in this case I do not see sometext. What am I missing?
To explain in more detail - I want pipe dir contents to FH, and following that I want to append some text to the same filehandle FH . I have referred link http://perldoc.perl.org/perlopentut.html#Pipe-Opens
You are not redirecting anything: You are piping your script's output to either the cmd.exe builtin dir or an alias to ls depending on your OS (which means, you might run into trouble if you run this script with Cygwin's ls in your path on Windows).
Writing to dir does not seem useful. If you wanted to filter dirs output, i.e. take the output from running dir and manipulate it before printing, you should pipe it into your script and you should print the processed output.
#!/usr/bin/env perl
use strict; use warnings;
my $pid = open my $dir_out, '-|', 'cmd.exe /c dir';
die "Cannot open pipe: $!\n" unless $pid;
my $output_file = 'output.txt';
open my $my_out, '>', $output_file
or die "Cannot open '$output_file': $!";
while (my $line = <$dir_out>) {
$line =~ s/bytes free/peons liberated/;
print $my_out $line;
}
close $my_out
or die "Cannot close '$output_file': $!";
close $dir_out
or die "Cannot close pipe: $!\n";
Of course, I am assuming there are other things going on in your program and this is only a small part of it. Otherwise, you don't need to write this much code for a simple filter.
You cannot write to FH with print and then expect to read from FH in the next statement. File handles are not FIFOs (by default).
The open gives you a writable file handle, the read end of which is connected to the stdin of dir. Reading from a write file handle just gives you nothing.
What do you actually want to achieve? Send some text to the dir program, or read output of the dir program?
Since in the comment you said you want to read the output of the dir command, you have the open command wrong; use "dir |" instead of "| dir" and read the Perl Open Tutorial.
Maybe this does what you want to do:
open (FH, "dir|") or die "$OS_ERROR";
while (<FH>){
print;
}
print "sometext\n";

Perl pipe and C process as child [Windows ]

I want to fork a child ( which is my C executable ) and share a pipe between perl and C process,
Is it possible to have STDOUT and STDIN to use as pipe.
Tried with following code but child process keep continue running.
use IPC::Open2;
use Symbol;
my $CHILDPROCESS= "chile.exe";
$WRITER = gensym();
$READER = gensym();
my $pid = open2($READER,$WRITER,$CHILDPROCESS);
while(<STDIN>)
{
print $WRITER $_;
}
close($WRITER);
while(<$READER>)
{
print STDOUT "$_";
}
The Safe Pipe Opens section of the perlipc documentation describes a nice feature for doing this:
The open function will accept a file argument of either "-|" or "|-" to do a very interesting thing: it forks a child connected to the filehandle you've opened. The child is running the same program as the parent. This is useful for safely opening a file when running under an assumed UID or GID, for example. If you open a pipe to minus, you can write to the filehandle you opened and your kid will find it in his STDIN. If you open a pipe from minus, you can read from the filehandle you opened whatever your kid writes to his STDOUT.
But according to the perlport documentation
open
open to |- and -| are unsupported. (Win32, RISC OS)
EDIT: This might only work for Linux. I have not tried it for Windows. There might be a way to emulate it in Windows though.
Here is what you want I think:
#Set up pipes to talk to the shell.
pipe(FROM_PERL, TO_C) or die "pipe: $!\n";
pipe(FROM_C, TO_PERL) or die "pipe: $!\n";
#auto flush so we don't have (some) problems with deadlocks.
TO_C->autoflush(1);
TO_PERL->autoflush(1);
if($pid = fork()){
#parent
close(FROM_PERL) or die "close: $!\n";
close(TO_PERL) or die "close: $!\n";
}
else{
#child
die "Error on fork.\n" unless defined($pid);
#redirect I/O
open STDIN, "<&FROM_PERL";
open STDOUT, ">&TO_PERL";
open STDERR, ">&TO_PERL";
close(TO_C) or die "close: $!\n";
close(FROM_C) or die "close $!\n";
exec("./cprogram"); #start program
}
Now you can communicate to the shell via FROM_C and TO_C as input and output, respectively.
This Q&A over on Perlmonks suggests that open2 runs fine on Windows, provided you manage it carefully enough.

How can I redirect the output of Perl's system() to a filehandle?

With the open command in Perl, you can use a filehandle. However I have trouble getting back the exit code with the open command in Perl.
With the system command in Perl, I can get back the exit code of the program I'm running. However I want to just redirect the STDOUT to some filehandle (no stderr).
My stdout is going to be a line-by-line output of key-value pairs that I want to insert into a mao in perl. That is why I want to redirect only my stdout from my Java program in perl. Is that possible?
Note: If I get errors, the errors get printed to stderr. One possibility is to check if anything gets printed to stderr so that I can quite the Perl script.
Canonically, if you're trying to get at the text output of a forked process, my understanding is that's what the backticks are for. If you need the exit status as well, you can check it with the $? special variable afterward, e.g.:
open my $fh, '>', "output.txt" or die $!;
print {$fh} `echo "Hello!"`;
print "Return code: $?\n";
Output to STDERR from the command in backticks will not be captured, but will instead be written directly to STDERR in the Perl program it's called from.
You may want to check out IPC::System::Simple -- it gives you many options for executing external commands, capturing its output and return value, and optionally dying if a bad result is returned.
This is one of the ways to do it.
open my $fh, '>', $file;
defined(my $pid = fork) or die "fork: $!";
if (!$pid) {
open STDOUT, '>&', $fh;
exec($command, #args);
}
waitpid $pid, 0;
print $? == 0 ? "ok\n" : "nok\n";
Use open in -| mode. When you close the filehandle, the exit status will be in $?.
open my $fh, '-|', "$command"; # older version: open my $fh, "$command |";
my #command_output = <$fh>;
close $fh;
my $command_status = $?;
From perldoc -f close
If the file handle came from a piped open, "close" will
additionally return false if one of the other system calls
involved fails, or if the program exits with non-zero status.
(If the only problem was that the program exited non-zero, $!
will be set to 0.) Closing a pipe also waits for the process
executing on the pipe to complete, in case you want to look at
the output of the pipe afterwards, and implicitly puts the exit
status value of that command into $? and
"${^CHILD_ERROR_NATIVE}".

How can I redirect the output from one filehandle into another?

I want to set up a pipeline of processes from within Perl (running on Linux), consisting of two parts run at separate times.
Eg:
Start the consumer process:
open( OUT, "| tar xvf - " ) || die "Failed: tar: $!";
then much later start the producer process:
open( IN, "gpg -d $file |" ) || die "Failed: gpg: $!";
but then somehow redirect the output from gpg into the input to tar.
I can do this by building a loop:
while (<IN> ) {
print OUT;
}
But I would like to know if I can somehow glue the two processes together with redirection.
Add
pipe( IN, OUT );
Before the two open statements. That's it!
If you want to do anything more complicated, I would recommend the IPC::Run CPAN module:
http://search.cpan.org/dist/IPC-Run/
It lets you start processes, tie their input and outputs together, and add logging or redirection at any point in the chain.
If the two processes are completely unrelated, use a FIFO.
use POSIX qw(mkfifo);
mkfifo($path, 0700) or die "mkfifo $path failed: $!";
This creates a FIFO at $path. Now have one process write to that file, and the other process read from it.
I like Proc::SafeExec it lets you tie together processes and file handles in almost arbitrary ways easily. Here's an example:
use strict;
use warnings;
use Proc::SafeExec;
open(my $ls, "-|", "ls", "-l") or die "Err: $!";
open(my $fh, ">", "tmp.txt") or die "Err: $!";
my $p = Proc::SafeExec->new({
exec => [qw(sed -e s/a/b/)],
stdin => $ls,
stdout => $fh,
});
$p->wait();
After looking at IPC::Run, it looks a lot simpler...here's the same example using IPC::Run instead:
use IPC::Run qw(run);
run [qw(ls -l)], "|", [qw(sed -e s/a/b/)], ">", "tmp.txt";