Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl: Pass open file handle to a program reading STDIN

I read a few lines from STDIN. How can I pass the remaining of STDIN to a command that reads from standard input (e.g. md5sum or wc)?

I could do a:

read_a_few_lines_from_diamond_operator();
open (C, "|cmd");
while(<>) { print C }
close C;
cleanup_after_C();

but for efficency reasons I would like not to touch the input, but instead pass the filehandle of STDIN. Sort of like:

seq 10 | (read A; wc)

where read reads as much as it likes before passing the rest on to wc. I cannot use this solution, though, as I need to start the command from inside my perl program and I need to do work after cmd completes.


I read a few lines from the file 'foo'. How can I pass the remaining to a command that reads from standard input (e.g. md5sum or wc)?

I could do a:

open (F, "<foo");
read_a_few_lines_from_F();
open (C, "|cmd");
while(<F>) { print C }
close C;
cleanup_after_C();

but for efficency reasons I would like not to touch the input, but instead pass the rest of file 'foo'.


I have a feeling it can to be done using trickery like select, open(FOO,">&STDOUT), exec 6<&0, fork, pipe.

like image 545
Ole Tange Avatar asked Aug 30 '12 23:08

Ole Tange


1 Answers

The answer is quite simple: You don't need to do anything special. Your child will automatically inherit your STDIN with system and exec. Everything you haven't read from STDIN will be readable by the child.

There is a problem, though. Because reading one character at a time would be crazy inefficient, Perl reads from the file a block at a time. That is to say you read more from the file than the "few lines" you got back from Perl. This can clearly be seen using the following command:

perl -E'say $_ x 500 for "a".."z"' \
   | perl -e'<>; <>; exec("cat");' \
   | less

Instead of starting at the start of the second line, cat starts in the middle of the "q"s (at byte 8192)!

You'd have to switch from reading lines with readline (<>) to reading individual bytes with sysread if you wanted this to work.


Focusing on the bigger picture, I think there is a solution:

open(STDIN, "<", "foo") or die $!;
read_a_few_lines(*STDIN);
my $pos = tell(STDIN);
open(STDIN, "<", "foo") or die $!;
sysseek(STDIN, $pos, SEEK_SET);
system(@cmd);
...

Or maybe even:

open(STDIN, "<", "foo") or die $!;
read_a_few_lines(*STDIN);
sysseek(STDIN, tell(STDIN), SEEK_SET);
system(@cmd);
...

Untested.

like image 156
ikegami Avatar answered Sep 20 '22 16:09

ikegami