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

Hi

I am looking for someone who can provide a shorter and nicer way of either getting input from STDIN or from file. And if there is no input from STDIN return help page.

Ex. of my ugly version.

use IO::Select; # Get a list of hosts from either a file or STDIN my @hosts; if($#ARGV != -1) { my $file = $ARGV[0]; if(! -f $file) { die "File $file doesn't exist\n" } open FH, "<$file" or die "Failed to open file $file: $!\n"; @hosts = <FH>; close FH; } else { my $sel = IO::Select->new(); $sel->add(\*STDIN); my @ready = $sel->can_read(0); if($#ready == -1) { die <<__USAGE__; usage: do_something [file] ... __USAGE__ } @hosts = <STDIN>; }

Replies are listed 'Best First'.
Re: Nicest way of getting STDIN a beauty contest
by ELISHEVA (Prior) on Oct 09, 2009 at 10:23 UTC

    To get input from either STDIN or a file, use a variable to store the file handle, rather than using FH or STDIN directly. That way you can use the exact same code to handle both cases - reading from STDIN and reading from a file.

    Additionally, I would stay away from slurping, i.e. reading everything into an array at once: @hosts = <STDIN>. It doesn't scale well and even for small data sets, it doesn't give you much opportunity to clean up the data before you put it in the @host array. For example, you might want to get rid of blank lines or trailing new lines.

    use strict; use warnings; # set input file handle my $fh; if (scalar(@ARGV)) { # Note: 3 parameter open - this is much safer. my $file = $ARGV[0]; open($fh, '<', $file) or die "Failed to open file $file: $!\n"; } else { $fh = \*STDIN; } # read in the data my @hosts; while (my $line=<$fh>) { # get rid of trailing new line on each hosts name chomp $line; # skip blank lines next unless $line; # add host to list of hosts push @hosts, $line; } # print it results if (scalar(@hosts)) { # we have input so print it out local $"='|'; print "hosts found: |@hosts|\n"; } else { # if @hosts is empty it isn't exactly a syntax error. Users will # be confused if you *only* print out the usage statement. if (scalar(@ARGV)) { # we read from a file but it was empty printf STDERR "file $ARGV[0] was empty!\n"; } else { # oops no input! and we read from STDIN printf STDERR "I expected input from STDIN and got nothing!\n"; } die <<__USAGE__; usage: do_something [file] ... __USAGE__ }

    To play with this script and see how it handles STDIN you can either run it by piping data ( echo -e "a\nb\nc\n" | perl MyScript.pm ) or by just running perl MyScript.pm). If you do it the second way, you must stop entering input using Ctrl-D on Linux. If you use Ctrl-C (like I sometimes do), the script will abort before it ever prints out the list of hosts at the end.

    Best, beth

    Update: Forgot to include usage statement for case when there is no data - added.

    Update 2: Changed error message to clarify nature of the problem.

Re: Nicest way of getting STDIN a beauty contest
by ikegami (Patriarch) on Oct 09, 2009 at 14:59 UTC

    The problem is the unusual requirement "And if there is no input from STDIN return help page."

    Drop it.

    Add the couple of lines needed to support -h and --help instead, and you have a standard unix tool. See Getopt::Long

Re: Nicest way of getting STDIN a beauty contest
by Anonymous Monk on Oct 09, 2009 at 08:46 UTC
    #!/usr/bin/perl -- use strict; use warnings; my @hosts; while(<>){ push @hosts, $_; } if( @hosts ){ print "Read hosts @hosts\n"; } else { die "usage: do_something [file]\n"; }
Re: Nicest way of getting STDIN a beauty contest
by Anonymous Monk on Oct 09, 2009 at 08:49 UTC

      It will wait in perpetuity before exiting if no input was provided to STDIN and no file was specified.

      So yes magic <> would have worked if I didn't want it to exit.

      If that was all I wanted a simple ...below... would have worked.

      my @hosts = <>;
        It will wait in perpetuity before exiting if no input was provided to STDIN and no file was specified.

        How long do you want it to wait? Your program has to wait until I'm done with the current line. How does it tell that I've not just gone away for a coffee break?

        However, if STDIN is closed (e.g. by typing ^D at a terminal, or by closing a pipe after no input) the simple solution

        chomp( my @hosts = <>); die "usage: foo hosts\n" unless @hosts;

        will do what you want.

Re: Nicest way of getting STDIN a beauty contest
by shmem (Chancellor) on Oct 09, 2009 at 09:21 UTC

    update: bah, needlessly complicated. Just use <> and check the result.

    I'd do it like this:

    #!/usr/bin/perl use strict; use warnings; my @hosts; while(<>){ chomp; die "usage: do_something [file]\n" if $ARGV eq '-' and ! $_; next unless $_; push @hosts, $_; } print "Read hosts:\n"; print $_,$/ for @hosts;

    That will bail at an empty input line from STDIN. Works with

    do_something hosts do_something < hosts echo foo | do_something do_something type type type
      It's a nice approach but it still doesn't bail when you simply type...
      do_something

      It will then wait for input until you do CTRL+D. In the first example it will exit immediately if nothing is available in STDIN.

      But doing that either involves using "select" or "IO::Select". Using either if those the solution quickly becomes quite ugly for a quite simple thing.

Re: Nicest way of getting STDIN a beauty contest
by mickep76 (Beadle) on Oct 09, 2009 at 09:37 UTC

    I guess I was unclear... If you simply run the command without anything it should exit and not wait for input.

    The example provided does this but it's not exactly pretty.

      die "oops" if !@ARGV && -t STDIN; my @hosts = <>;
        This works nicely, thanks a lot that saves the day from ugly code.
      perl -lne "warn -t STDIN" perl -lne "warn -t STDIN" < file echo | perl -lne "warn -t STDIN"