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

Many of my scripts process data piped in from STDIN, but often times I want to also run the script with a small amount of data for maintenance or testing. I could just use echo for a few items, but I sometimes forget to pipe and add the data as command-line arguments, until I notice the script is blocking on the missing STDIN. To avoid this, I've been using the following bit of code to easily handle input from either @ARGV OR STDIN:
my @ids = do { @ARGV || -t STDIN ? @ARGV : <STDIN> }; chomp @ids;
But recently I noticed for a long-running process it wasn't doing any work until the data was all collected, so I modified the code to process data from STDIN as it's received:
my $fh; if (@ARGV || -t STDIN) { open $fh, '<', \ join("\n", @ARGV); } else { $fh = *STDIN } while (defined(my $id = <$fh>)) { chomp $id; }
Is there a better idiom to handle this?
Note: the null filehandle (<>) does not do what I want since I don't want it interpreting @ARGV as a list of filenames to read data.

Replies are listed 'Best First'.
Re: Getting data from either ARGV or STDIN
by LanX (Saint) on Mar 11, 2021 at 23:03 UTC
    That's quite cleverly done.

    Just, according to -X is -t defaulting to STDIN. So you could spare a word.

    2 other alternatives come to mind ...

    ... iterator

    An alternative approach which seems cleaner to me could be to create an iterator which either implements:

    sub iter { shift @ARGV } °

    or

    sub iter {scalar <STDIN>} *

    And use it in the loop

    while ( ($id) = iter() ) { ... }

    ... override

    Another way could be to override readline (which is the iterator behind <> ) on demand to iterate over @ARGV.

    Both ideas untested since I'm posting from mobile.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    °) Could be that you need

    sub iter { shift @ARGV //()}

    instead, I'm afraid shift will return undef if the array is exhausted.

    *) updated scalar

      Just according to -X is -t defaulting to STDIN so you could spare a word.
      True, but I'd probably have to keep looking that up to remind myself what it's doing. I was actually thinking of spending another char and switching to !-p STDIN since that's a bit more self-documenting.

      An alternative approach which seems cleaner to me could be to create an iterator which either implements:
      Excellent suggestion! It's both more succinct and understandable.
      my $iter = (@ARGV || -t STDIN) ? sub { shift @ARGV } : sub { <STDIN> } +; while (defined(my $in = $iter->())) { }

      Thanks!
        No matter which solution you choose in the end, you might want to hide the magic behind a small module with more documentation.

        Like this, you'll only need something like use ReadARGV; in your scripts

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

        Maybe shorter

        *iter = (@ARGV || -t STDIN) ? sub { shift @ARGV } : sub { <STDIN> }; while (defined(my $in = iter() )) { }

        > Thanks!

        Your welcome. :)

        update

        renaming iter to input may be more self documenting.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery