Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Multiple STDIN sources

by perlfan (Curate)
on Mar 09, 2004 at 23:50 UTC ( #335294=perlquestion: print w/ replies, xml ) Need Help??
perlfan has asked for the wisdom of the Perl Monks concerning the following question:

I have a script that I want to direct an input stream to using either "|":
% cat myfile.txt | myprogram.pl
or "<":
% myprogram.pl < myfile.txt
The problem I have is that in the code I also ask for user input, and assign the answer to the question using <STDIN>. However the STDIN stream conflict, and I can not figure out how to "reset" STDIN. Here is my code that shows the problem:
#!/usr/bin/perl -w use strict; my $stream; while (<>) { $stream .= $_; } print "Do you want to process the stream?"; my $ans = <STDIN>; #... exit;
Basically, perl blows right past the "my $ans = <STDIN>;" line. I have searched high and low - both here and on the web, and can not find the solution - do I need to reset STDIN? Doing a "close(STDIN)" causes errors, so I am not sure what to do. Thanks! Brett

Comment on Multiple STDIN sources
Select or Download Code
Re: Multiple STDIN sources
by Abigail-II (Bishop) on Mar 10, 2004 at 00:18 UTC
    Basically, perl blows right past the "my $ans = <STDIN>;" line.
    Really? Not for me. I've added the following lines before your exit:
    chomp $ans; print "Got '$ans'\n"; print "stream = $stream";
    And this is what I get when running:
    $ ./stdin foo bar ^D Do you want to process the stream?yes Got 'yes' stream = foo bar $

    Abigail

      This is because it isn't reading it from a pipe in your test: it's reading it all from user input (as you typed in text followed by a control-D). If you do something like echo foo | ./stdin it does blow right past the prompt like the OP said.
        Oh, I see. In that case, just reopen STDIN, and read from the terminal:
        #!/usr/bin/perl -w use strict; $| = 1; my $stream; while (<>) { $stream .= $_; } open STDIN, "/dev/tty" or die; print "Do you want to process the stream? "; my $ans = <STDIN>; chomp $ans; print "Got '$ans'\n"; print "stream = $stream"; #... exit; __END__ $ echo foo | ./stdin Do you want to process the stream? yes Got 'yes' stream = foo $
        Abigail
Re: Multiple STDIN sources
by TomDLux (Vicar) on Mar 10, 2004 at 00:21 UTC

    You can either connect STDIN to a file or to the user TTY, not both. It's like saying you can wear a bowtie or a necktie, but not both.

    The simplest solution is probably to specify the name of the file, and use STDIN for user interaction. The name of the file is in @ARGV.

    open( INFILE, "<$ARGV[0]" ) or die "Culd not open $ARGV[0] $!"; .... close INFILE;

    --
    TTTATCGGTCGTTATATAGATGTTTGCA

      You can either connect STDIN to a file or to the user TTY, not both. It's like saying you can wear a bowtie or a necktie, but not both.
      But you can wear a bowtie after you've taken off the necktie. See my other post.

      Abigail

Re: Multiple STDIN sources
by bsb (Priest) on Mar 10, 2004 at 03:13 UTC
    You've got a technical solution in other posts. So I'll just recommend spliting your program into a just_do_it.pl and confirmation_wrapper.pl

    iselect solves a similar problem in a general way but may not be useful to you directly.

Re: Multiple STDIN sources
by graff (Chancellor) on Mar 10, 2004 at 03:54 UTC
    I'm guessing that this bit in your post was just by way of example:
    print "Do you want to process the stream?"; my $ans = <STDIN>;
    I mean, you wouldn't really need that sort of prompt in a real script: if the person didn't want to process the stream, they wouldn't run the program in the first place, so why ask this kind of question at all? I could imagine many cases where the user needs to provide some kind of decision about how the stream should be processed, and maybe sometimes the user needs some sort of report about the stream before making the decision.

    As a rule, stream-based processes handle user input by means of command-line options or flags (e.g. "-q" to suppress warnings or progress reports, "-f" to continue processing despite warnings, "-r" to randomize the output, or whatever...) This is perfectly sensible in the typical case where the user is able to decide, before starting the process, what sort of option they want to use on the given run. If the user needs some info about the stream before making the decision, this might require a separate run, using the script in "probe" or "no_op" mode (or running a separate reporting script on the stream) to get the needed information before doing the processing run with their decision provided on the command line.

    On a separate note, you might save some run-time if you change this:

    while (<>) { $stream .= $_; }
    to this:
    { local $/ = undef; $stream = <>; # slurp all the data in one read }
Re: Multiple STDIN sources
by TilRMan (Friar) on Mar 10, 2004 at 04:07 UTC

    Your code can work as-is if you can change the way you call the script. Instead of a shell redirect (myprogram.pl < myfile.txt), leave out the < and let the diamond do the work (leaving STDIN alone):

    myprogram.pl myfile.txt

    Instead of a shell pipeline (some_process | some_filter | myprogram.pl), let the magical open (inside the diamond) do the work:

    myprogram.pl 'some_process | some_filter |'

    BTW, for efficiency, you might want to slurp <> instead of repeatedly concatenating lines. (Reallocation of strings and all that.... Does anyone have a link to a quasi-official explanation of why it's more efficient?)

    -- 
    LP^>

Re: Multiple STDIN sources
by raymor (Acolyte) on Mar 10, 2004 at 08:29 UTC
    This is a common thing. Various Linux tools do this all the time, so I'm not sure why the "standard" answer wasn't posted within minutes. /dev/tty is an alias for the controlling terminal:
    while (<>) { $stream .= $_; } open TTY, "</dev/tty"; print "Do you want to process the stream?"; my $ans = <TTY>;
    Alternatively, you can actually read from STDERR, just as you might print status or error messges to STDERR. If your reading inout from stdin, you might want to be able to write output to stdout too, rather than having your prompts go to stdout. You can print your prompts to STDERR or open the tty read/write "+</dev/tty".
    ~~~~~~~~~~~~~~~
    Ray Morris
    support@webmastersguide.com
      Thank you all ... at some point I need to find a xp solution so it will work on non-*nix, but that is not a concern right now.
        that's easy enough, fortunately. just change the open line from Abigail's code to read:
        if($^O eq 'MSWin32') { open(STDIN, "CON") or die $!; } else { open(STDIN, "/dev/tty") or die $!; }
        cheers,
        Aldo

        King of Laziness, Wizard of Impatience, Lord of Hubris

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (5)
As of 2014-08-29 02:37 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The best computer themed movie is:











    Results (275 votes), past polls